We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
B-Plan API: tile picture not transferred from Imperia to mB; bplan not published
**URL:** see below
**user:** bplan initiator via imperia API
**expected behaviour:** the tile picture entered via Imperia is shown on mB, the plan is published and not only saved as draft
**behaviour:** sometimes the picture is not shown, sometimes the plan does not get automatically published
**important screensize:**
**device & browser:** 
**Comment/Question:** it does not happen always but sometimes with different districts involved. Prio set as high as we need to find a solution but not release right away as there is a workaround.

list of bplans that recently had problems (as far as I can read from mails as I wasn't always included directly into the support):
https://mein.berlin.de/django-admin/meinberlin_bplan/bplan/1535 (May 2022, not published)
https://mein.berlin.de/django-admin/meinberlin_bplan/bplan/1488/change/ (March 22, not published)
https://mein.berlin.de/django-admin/meinberlin_bplan/bplan/1476/change/ (March 22, picture not transferred)
https://mein.berlin.de/django-admin/meinberlin_bplan/bplan/1502/change/ (April 22, not published, picture not transferred)
https://mein.berlin.de/django-admin/meinberlin_bplan/bplan/1449/change/ (Feb 22, picture not transferred)


Regarding 1449, we have already been in contact with BO, they write the following: 

beim übermitteln der Daten aus dem Imperia-System bekommen wir ein
  "HTTP/2 400"

Wäre schön wenn wir bei diesen 400 immer auch gleich eine Error-Meldung mit bekommen könnten, das ist aber immer nur ein schwarzes Loch.

Folgender POST-Request schlägt fehl

{"name":"Bebauungsplan XIV-3-1 (\u201eNeuk\u00f6llnische Allee \/ Schmalenbachstra\u00dfe\u201c)","url":"https:\/\/[www.berlin.de](http://www.berlin.de/)\/ba-neukoelln\/politik-und-verwaltung\/aemter\/stadtentwicklungsamt\/stadtplanung\/bebauungsplaene\/bebauungsplan-fuer-meinberlin\/bebauungsplan.1176597.php","office_worker_email":"[stadtplanung@bezirksamt-neukoelln.de](mailto:stadtplanung@bezirksamt-neukoelln.de)","start_date":"2022-02-21 00:00","end_date":"2022-03-11 23:59","description":"Sicherung und Stabilisierung eines Gewerbegebietes durch den Ausschluss unerw\u00fcnschter Einzelnutzungen.","identifier":"XIV-3-1","is_draft":"False","image_url":"https:\/\/[www.berlin.de](http://www.berlin.de/)\/imgscaler\/r9Iz3AbUfqVDjoWe7B-GLwXcCWHjBVGlMLWfoeyqvwc\/meinberlin\/L3N5czExLXByb2QvYmEtbmV1a29lbGxuL3BvbGl0aWstdW5kLXZlcndhbHR1bmcvYWVtdGVyL3N0YWR0ZW50d2lja2x1bmdzYW10L3N0YWR0cGxhbnVuZy9iZWJhdXVuZ3NwbGFlbmUvYmViYXV1bmdzcGxhbi1mdWVyLW1laW5iZXJsaW4vdW50ZXJsYWdlbi9rYXJ0ZW4tdW5kLXBsYWVuZS91cGxvYWRfXzgzZWIwZjA4OGNjZmEwMTUxNjk0YTcyODQzZWJjYjA1X3hpdi0zLTFfMTAwMDB1ZWJlcnNpY2h0LmpwZw.jpg","image_copyright":"Bezirksamt Neuk\u00f6lln von Berlin"}


Wahrscheinlich ist es die "image_url"
Mit "\\" sind es 408 Zeichen
Ohne "\\" sind es 402 Zeichen

In der API-Beschreibung auf https://github.com/liqd/a4-meinberlin/blob/main/docs/bplan_api.md ist keine Pfad-Längenbeschränkung dokumentiert.
Die Bildgröße ist korrekt.

https://www.berlin.de/imgscaler/r9Iz3AbUfqVDjoWe7B-GLwXcCWHjBVGlMLWfoeyqvwc/meinberlin/L3N5czExLXByb2QvYmEtbmV1a29lbGxuL3BvbGl0aWstdW5kLXZlcndhbHR1bmcvYWVtdGVyL3N0YWR0ZW50d2lja2x1bmdzYW10L3N0YWR0cGxhbnVuZy9iZWJhdXVuZ3NwbGFlbmUvYmViYXV1bmdzcGxhbi1mdWVyLW1laW5iZXJsaW4vdW50ZXJsYWdlbi9rYXJ0ZW4tdW5kLXBsYWVuZS91cGxvYWRfXzgzZWIwZjA4OGNjZmEwMTUxNjk0YTcyODQzZWJjYjA1X3hpdi0zLTFfMTAwMDB1ZWJlcnNpY2h0LmpwZw.jpg
Bildgröße: 800x600  (ist Y als die Minimalgröße 500x300)
Bildgröße <10MB ist a cuh erfüllt: 124332 Bytes


Bitte überprüfen, ob es an image_url liegt
und bitte das das Feld bis 512 Zeichen Länge erlauben

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `meinberlin/apps/bplan/serializers.py`
Content:
```
1 import datetime
2 import imghdr
3 import posixpath
4 import tempfile
5 from urllib.parse import urlparse
6 
7 import requests
8 from django.apps import apps
9 from django.conf import settings
10 from django.contrib.sites.models import Site
11 from django.core.exceptions import ValidationError
12 from django.core.files.images import ImageFile
13 from django.urls import reverse
14 from django.utils import timezone
15 from django.utils.translation import gettext as _
16 from rest_framework import serializers
17 
18 from adhocracy4.dashboard import components
19 from adhocracy4.dashboard import signals as a4dashboard_signals
20 from adhocracy4.images.validators import validate_image
21 from adhocracy4.modules import models as module_models
22 from adhocracy4.phases import models as phase_models
23 from adhocracy4.projects import models as project_models
24 
25 from .models import Bplan
26 from .phases import StatementPhase
27 
28 BPLAN_EMBED = '<iframe height="500" style="width: 100%; min-height: 300px; ' \
29               'max-height: 100vh" src="{}" frameborder="0"></iframe>'
30 DOWNLOAD_IMAGE_SIZE_LIMIT_BYTES = 10 * 1024 * 1024
31 
32 
33 class BplanSerializer(serializers.ModelSerializer):
34     id = serializers.IntegerField(required=False)
35 
36     # make write_only for consistency  reasons
37     start_date = serializers.DateTimeField(write_only=True)
38     end_date = serializers.DateTimeField(write_only=True)
39     image_url = serializers.URLField(
40         required=False,
41         write_only=True,
42         max_length=(project_models.Project._meta.
43                     get_field('tile_image').max_length))
44     image_copyright = serializers.CharField(
45         required=False,
46         write_only=True,
47         source='tile_image_copyright',
48         allow_blank=True,
49         max_length=(project_models.Project._meta.
50                     get_field('tile_image_copyright').max_length))
51     embed_code = serializers.SerializerMethodField()
52 
53     class Meta:
54         model = Bplan
55         fields = (
56             'id', 'name', 'identifier', 'description', 'url',
57             'office_worker_email', 'is_draft', 'start_date', 'end_date',
58             'image_url', 'image_copyright', 'embed_code'
59         )
60         extra_kwargs = {
61             # write_only for consistency reasons
62             'is_draft': {'default': False, 'write_only': True},
63             'name': {'write_only': True},
64             'description': {'write_only': True},
65             'url': {'write_only': True},
66             'office_worker_email': {'write_only': True},
67             'identifier': {'write_only': True}
68         }
69 
70     def create(self, validated_data):
71         orga_pk = self._context.get('organisation_pk', None)
72         orga_model = apps.get_model(settings.A4_ORGANISATIONS_MODEL)
73         orga = orga_model.objects.get(pk=orga_pk)
74         validated_data['organisation'] = orga
75 
76         start_date = validated_data['start_date']
77         end_date = validated_data['end_date']
78 
79         image_url = validated_data.pop('image_url', None)
80         if image_url:
81             validated_data['tile_image'] = \
82                 self._download_image_from_url(image_url)
83 
84         bplan = super().create(validated_data)
85         self._create_module_and_phase(bplan, start_date, end_date)
86         self._send_project_created_signal(bplan)
87         return bplan
88 
89     def _create_module_and_phase(self, bplan, start_date, end_date):
90         module = module_models.Module.objects.create(
91             name=bplan.slug + '_module',
92             weight=1,
93             project=bplan,
94         )
95 
96         phase_content = StatementPhase()
97         phase_models.Phase.objects.create(
98             name=_('Bplan statement phase'),
99             description=_('Bplan statement phase'),
100             type=phase_content.identifier,
101             module=module,
102             start_date=start_date,
103             end_date=end_date
104         )
105 
106     def update(self, instance, validated_data):
107         start_date = validated_data.get('start_date', None)
108         end_date = validated_data.get('end_date', None)
109         if start_date or end_date:
110             self._update_phase(instance, start_date, end_date)
111             if end_date and end_date > timezone.localtime(timezone.now()):
112                 instance.is_archived = False
113 
114         image_url = validated_data.pop('image_url', None)
115         if image_url:
116             validated_data['tile_image'] = \
117                 self._download_image_from_url(image_url)
118 
119         instance = super().update(instance, validated_data)
120 
121         self._send_component_updated_signal(instance)
122         return instance
123 
124     def _update_phase(self, bplan, start_date, end_date):
125         module = module_models.Module.objects.get(project=bplan)
126         phase = phase_models.Phase.objects.get(module=module)
127         if start_date:
128             phase.start_date = start_date
129         if end_date:
130             phase.end_date = end_date
131         phase.save()
132 
133     def get_embed_code(self, bplan):
134         url = self._get_absolute_url(bplan)
135         embed = BPLAN_EMBED.format(url)
136         return embed
137 
138     def _get_absolute_url(self, bplan):
139         site_url = Site.objects.get_current().domain
140         embed_url = reverse('embed-project', kwargs={'slug': bplan.slug, })
141         url = 'https://{}{}'.format(site_url, embed_url)
142         return url
143 
144     def _download_image_from_url(self, url):
145         parsed_url = urlparse(url)
146         file_name = None
147         try:
148             r = requests.get(url, stream=True, timeout=10)
149             downloaded_bytes = 0
150             with tempfile.TemporaryFile() as f:
151                 for chunk in r.iter_content(chunk_size=1024):
152                     downloaded_bytes += len(chunk)
153                     if downloaded_bytes > DOWNLOAD_IMAGE_SIZE_LIMIT_BYTES:
154                         raise serializers.ValidationError(
155                             'Image too large to download {}'.format(url))
156                     if chunk:
157                         f.write(chunk)
158                 file_name = self._generate_image_filename(parsed_url.path, f)
159                 self._image_storage.save(file_name, f)
160         except Exception:
161             if file_name:
162                 self._image_storage.delete(file_name)
163             raise serializers.ValidationError(
164                 'Failed to download image {}'.format(url))
165 
166         try:
167             self._validate_image(file_name)
168         except ValidationError as e:
169             self._image_storage.delete(file_name)
170             raise serializers.ValidationError(e)
171 
172         return file_name
173 
174     def _validate_image(self, file_name):
175         image_file = self._image_storage.open(file_name, 'rb')
176         image = ImageFile(image_file, file_name)
177         config = settings.IMAGE_ALIASES.get('*', {})
178         config.update(settings.IMAGE_ALIASES['tileimage'])
179         validate_image(image, **config)
180 
181     @property
182     def _image_storage(self):
183         return project_models.Project._meta.get_field('tile_image').storage
184 
185     @property
186     def _image_upload_to(self):
187         return project_models.Project._meta.get_field('tile_image').upload_to
188 
189     def _generate_image_filename(self, url_path, file):
190         if callable(self._image_upload_to):
191             raise Exception('Callable upload_to fields are not supported')
192 
193         root_path, extension = posixpath.splitext(url_path)
194         if file:
195             # Workaround: imghdr expects the files position on 0
196             file.seek(0)
197             extension = imghdr.what(file) or 'jpeg'
198 
199         basename = posixpath.basename(root_path)
200         if not basename:
201             basename = 'bplan'
202 
203         dirname = datetime.datetime.now().strftime(self._image_upload_to)
204         filename = posixpath.join(dirname, basename + '.' + extension)
205 
206         return self._image_storage.get_available_name(filename)
207 
208     def _send_project_created_signal(self, bplan):
209         a4dashboard_signals.project_created.send(
210             sender=self.__class__,
211             project=bplan,
212             user=self.context['request'].user
213         )
214 
215     def _send_component_updated_signal(self, bplan):
216         component = components.projects['bplan']
217         a4dashboard_signals.project_component_updated.send(
218             sender=self.__class__,
219             project=bplan,
220             component=component,
221             user=self.context['request'].user
222         )
223
```
--- 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/meinberlin/apps/bplan/serializers.py b/meinberlin/apps/bplan/serializers.py
--- a/meinberlin/apps/bplan/serializers.py
+++ b/meinberlin/apps/bplan/serializers.py
@@ -39,15 +39,15 @@
     image_url = serializers.URLField(
         required=False,
         write_only=True,
-        max_length=(project_models.Project._meta.
-                    get_field('tile_image').max_length))
+    )
     image_copyright = serializers.CharField(
         required=False,
         write_only=True,
         source='tile_image_copyright',
         allow_blank=True,
         max_length=(project_models.Project._meta.
-                    get_field('tile_image_copyright').max_length))
+                    get_field('tile_image_copyright').max_length),
+    )
     embed_code = serializers.SerializerMethodField()
 
     class Meta:
@@ -196,9 +196,7 @@
             file.seek(0)
             extension = imghdr.what(file) or 'jpeg'
 
-        basename = posixpath.basename(root_path)
-        if not basename:
-            basename = 'bplan'
+        basename = 'bplan_%s' % (timezone.now().strftime('%Y%m%dT%H%M%S'))
 
         dirname = datetime.datetime.now().strftime(self._image_upload_to)
         filename = posixpath.join(dirname, basename + '.' + extension)

{"golden_diff": "diff --git a/meinberlin/apps/bplan/serializers.py b/meinberlin/apps/bplan/serializers.py\n--- a/meinberlin/apps/bplan/serializers.py\n+++ b/meinberlin/apps/bplan/serializers.py\n@@ -39,15 +39,15 @@\n     image_url = serializers.URLField(\n         required=False,\n         write_only=True,\n-        max_length=(project_models.Project._meta.\n-                    get_field('tile_image').max_length))\n+    )\n     image_copyright = serializers.CharField(\n         required=False,\n         write_only=True,\n         source='tile_image_copyright',\n         allow_blank=True,\n         max_length=(project_models.Project._meta.\n-                    get_field('tile_image_copyright').max_length))\n+                    get_field('tile_image_copyright').max_length),\n+    )\n     embed_code = serializers.SerializerMethodField()\n \n     class Meta:\n@@ -196,9 +196,7 @@\n             file.seek(0)\n             extension = imghdr.what(file) or 'jpeg'\n \n-        basename = posixpath.basename(root_path)\n-        if not basename:\n-            basename = 'bplan'\n+        basename = 'bplan_%s' % (timezone.now().strftime('%Y%m%dT%H%M%S'))\n \n         dirname = datetime.datetime.now().strftime(self._image_upload_to)\n         filename = posixpath.join(dirname, basename + '.' + extension)\n", "issue": "B-Plan API: tile picture not transferred from Imperia to mB; bplan not published\n**URL:** see below\r\n**user:** bplan initiator via imperia API\r\n**expected behaviour:** the tile picture entered via Imperia is shown on mB, the plan is published and not only saved as draft\r\n**behaviour:** sometimes the picture is not shown, sometimes the plan does not get automatically published\r\n**important screensize:**\r\n**device & browser:** \r\n**Comment/Question:** it does not happen always but sometimes with different districts involved. Prio set as high as we need to find a solution but not release right away as there is a workaround.\r\n\r\nlist of bplans that recently had problems (as far as I can read from mails as I wasn't always included directly into the support):\r\nhttps://mein.berlin.de/django-admin/meinberlin_bplan/bplan/1535 (May 2022, not published)\r\nhttps://mein.berlin.de/django-admin/meinberlin_bplan/bplan/1488/change/ (March 22, not published)\r\nhttps://mein.berlin.de/django-admin/meinberlin_bplan/bplan/1476/change/ (March 22, picture not transferred)\r\nhttps://mein.berlin.de/django-admin/meinberlin_bplan/bplan/1502/change/ (April 22, not published, picture not transferred)\r\nhttps://mein.berlin.de/django-admin/meinberlin_bplan/bplan/1449/change/ (Feb 22, picture not transferred)\r\n\r\n\r\nRegarding 1449, we have already been in contact with BO, they write the following: \r\n\r\nbeim \u00fcbermitteln der Daten aus dem Imperia-System bekommen wir ein\r\n  \"HTTP/2 400\"\r\n\r\nW\u00e4re sch\u00f6n wenn wir bei diesen 400 immer auch gleich eine Error-Meldung mit bekommen k\u00f6nnten, das ist aber immer nur ein schwarzes Loch.\r\n\r\nFolgender POST-Request schl\u00e4gt fehl\r\n\r\n{\"name\":\"Bebauungsplan XIV-3-1 (\\u201eNeuk\\u00f6llnische Allee \\/ Schmalenbachstra\\u00dfe\\u201c)\",\"url\":\"https:\\/\\/[www.berlin.de](http://www.berlin.de/)\\/ba-neukoelln\\/politik-und-verwaltung\\/aemter\\/stadtentwicklungsamt\\/stadtplanung\\/bebauungsplaene\\/bebauungsplan-fuer-meinberlin\\/bebauungsplan.1176597.php\",\"office_worker_email\":\"[stadtplanung@bezirksamt-neukoelln.de](mailto:stadtplanung@bezirksamt-neukoelln.de)\",\"start_date\":\"2022-02-21 00:00\",\"end_date\":\"2022-03-11 23:59\",\"description\":\"Sicherung und Stabilisierung eines Gewerbegebietes durch den Ausschluss unerw\\u00fcnschter Einzelnutzungen.\",\"identifier\":\"XIV-3-1\",\"is_draft\":\"False\",\"image_url\":\"https:\\/\\/[www.berlin.de](http://www.berlin.de/)\\/imgscaler\\/r9Iz3AbUfqVDjoWe7B-GLwXcCWHjBVGlMLWfoeyqvwc\\/meinberlin\\/L3N5czExLXByb2QvYmEtbmV1a29lbGxuL3BvbGl0aWstdW5kLXZlcndhbHR1bmcvYWVtdGVyL3N0YWR0ZW50d2lja2x1bmdzYW10L3N0YWR0cGxhbnVuZy9iZWJhdXVuZ3NwbGFlbmUvYmViYXV1bmdzcGxhbi1mdWVyLW1laW5iZXJsaW4vdW50ZXJsYWdlbi9rYXJ0ZW4tdW5kLXBsYWVuZS91cGxvYWRfXzgzZWIwZjA4OGNjZmEwMTUxNjk0YTcyODQzZWJjYjA1X3hpdi0zLTFfMTAwMDB1ZWJlcnNpY2h0LmpwZw.jpg\",\"image_copyright\":\"Bezirksamt Neuk\\u00f6lln von Berlin\"}\r\n\r\n\r\nWahrscheinlich ist es die \"image_url\"\r\nMit \"\\\\\" sind es 408 Zeichen\r\nOhne \"\\\\\" sind es 402 Zeichen\r\n\r\nIn der API-Beschreibung auf https://github.com/liqd/a4-meinberlin/blob/main/docs/bplan_api.md ist keine Pfad-L\u00e4ngenbeschr\u00e4nkung dokumentiert.\r\nDie Bildgr\u00f6\u00dfe ist korrekt.\r\n\r\nhttps://www.berlin.de/imgscaler/r9Iz3AbUfqVDjoWe7B-GLwXcCWHjBVGlMLWfoeyqvwc/meinberlin/L3N5czExLXByb2QvYmEtbmV1a29lbGxuL3BvbGl0aWstdW5kLXZlcndhbHR1bmcvYWVtdGVyL3N0YWR0ZW50d2lja2x1bmdzYW10L3N0YWR0cGxhbnVuZy9iZWJhdXVuZ3NwbGFlbmUvYmViYXV1bmdzcGxhbi1mdWVyLW1laW5iZXJsaW4vdW50ZXJsYWdlbi9rYXJ0ZW4tdW5kLXBsYWVuZS91cGxvYWRfXzgzZWIwZjA4OGNjZmEwMTUxNjk0YTcyODQzZWJjYjA1X3hpdi0zLTFfMTAwMDB1ZWJlcnNpY2h0LmpwZw.jpg\r\nBildgr\u00f6\u00dfe: 800x600  (ist Y als die Minimalgr\u00f6\u00dfe 500x300)\r\nBildgr\u00f6\u00dfe <10MB ist a cuh erf\u00fcllt: 124332 Bytes\r\n\r\n\r\nBitte \u00fcberpr\u00fcfen, ob es an image_url liegt\r\nund bitte das das Feld bis 512 Zeichen L\u00e4nge erlauben\n", "before_files": [{"content": "import datetime\nimport imghdr\nimport posixpath\nimport tempfile\nfrom urllib.parse import urlparse\n\nimport requests\nfrom django.apps import apps\nfrom django.conf import settings\nfrom django.contrib.sites.models import Site\nfrom django.core.exceptions import ValidationError\nfrom django.core.files.images import ImageFile\nfrom django.urls import reverse\nfrom django.utils import timezone\nfrom django.utils.translation import gettext as _\nfrom rest_framework import serializers\n\nfrom adhocracy4.dashboard import components\nfrom adhocracy4.dashboard import signals as a4dashboard_signals\nfrom adhocracy4.images.validators import validate_image\nfrom adhocracy4.modules import models as module_models\nfrom adhocracy4.phases import models as phase_models\nfrom adhocracy4.projects import models as project_models\n\nfrom .models import Bplan\nfrom .phases import StatementPhase\n\nBPLAN_EMBED = '<iframe height=\"500\" style=\"width: 100%; min-height: 300px; ' \\\n              'max-height: 100vh\" src=\"{}\" frameborder=\"0\"></iframe>'\nDOWNLOAD_IMAGE_SIZE_LIMIT_BYTES = 10 * 1024 * 1024\n\n\nclass BplanSerializer(serializers.ModelSerializer):\n    id = serializers.IntegerField(required=False)\n\n    # make write_only for consistency  reasons\n    start_date = serializers.DateTimeField(write_only=True)\n    end_date = serializers.DateTimeField(write_only=True)\n    image_url = serializers.URLField(\n        required=False,\n        write_only=True,\n        max_length=(project_models.Project._meta.\n                    get_field('tile_image').max_length))\n    image_copyright = serializers.CharField(\n        required=False,\n        write_only=True,\n        source='tile_image_copyright',\n        allow_blank=True,\n        max_length=(project_models.Project._meta.\n                    get_field('tile_image_copyright').max_length))\n    embed_code = serializers.SerializerMethodField()\n\n    class Meta:\n        model = Bplan\n        fields = (\n            'id', 'name', 'identifier', 'description', 'url',\n            'office_worker_email', 'is_draft', 'start_date', 'end_date',\n            'image_url', 'image_copyright', 'embed_code'\n        )\n        extra_kwargs = {\n            # write_only for consistency reasons\n            'is_draft': {'default': False, 'write_only': True},\n            'name': {'write_only': True},\n            'description': {'write_only': True},\n            'url': {'write_only': True},\n            'office_worker_email': {'write_only': True},\n            'identifier': {'write_only': True}\n        }\n\n    def create(self, validated_data):\n        orga_pk = self._context.get('organisation_pk', None)\n        orga_model = apps.get_model(settings.A4_ORGANISATIONS_MODEL)\n        orga = orga_model.objects.get(pk=orga_pk)\n        validated_data['organisation'] = orga\n\n        start_date = validated_data['start_date']\n        end_date = validated_data['end_date']\n\n        image_url = validated_data.pop('image_url', None)\n        if image_url:\n            validated_data['tile_image'] = \\\n                self._download_image_from_url(image_url)\n\n        bplan = super().create(validated_data)\n        self._create_module_and_phase(bplan, start_date, end_date)\n        self._send_project_created_signal(bplan)\n        return bplan\n\n    def _create_module_and_phase(self, bplan, start_date, end_date):\n        module = module_models.Module.objects.create(\n            name=bplan.slug + '_module',\n            weight=1,\n            project=bplan,\n        )\n\n        phase_content = StatementPhase()\n        phase_models.Phase.objects.create(\n            name=_('Bplan statement phase'),\n            description=_('Bplan statement phase'),\n            type=phase_content.identifier,\n            module=module,\n            start_date=start_date,\n            end_date=end_date\n        )\n\n    def update(self, instance, validated_data):\n        start_date = validated_data.get('start_date', None)\n        end_date = validated_data.get('end_date', None)\n        if start_date or end_date:\n            self._update_phase(instance, start_date, end_date)\n            if end_date and end_date > timezone.localtime(timezone.now()):\n                instance.is_archived = False\n\n        image_url = validated_data.pop('image_url', None)\n        if image_url:\n            validated_data['tile_image'] = \\\n                self._download_image_from_url(image_url)\n\n        instance = super().update(instance, validated_data)\n\n        self._send_component_updated_signal(instance)\n        return instance\n\n    def _update_phase(self, bplan, start_date, end_date):\n        module = module_models.Module.objects.get(project=bplan)\n        phase = phase_models.Phase.objects.get(module=module)\n        if start_date:\n            phase.start_date = start_date\n        if end_date:\n            phase.end_date = end_date\n        phase.save()\n\n    def get_embed_code(self, bplan):\n        url = self._get_absolute_url(bplan)\n        embed = BPLAN_EMBED.format(url)\n        return embed\n\n    def _get_absolute_url(self, bplan):\n        site_url = Site.objects.get_current().domain\n        embed_url = reverse('embed-project', kwargs={'slug': bplan.slug, })\n        url = 'https://{}{}'.format(site_url, embed_url)\n        return url\n\n    def _download_image_from_url(self, url):\n        parsed_url = urlparse(url)\n        file_name = None\n        try:\n            r = requests.get(url, stream=True, timeout=10)\n            downloaded_bytes = 0\n            with tempfile.TemporaryFile() as f:\n                for chunk in r.iter_content(chunk_size=1024):\n                    downloaded_bytes += len(chunk)\n                    if downloaded_bytes > DOWNLOAD_IMAGE_SIZE_LIMIT_BYTES:\n                        raise serializers.ValidationError(\n                            'Image too large to download {}'.format(url))\n                    if chunk:\n                        f.write(chunk)\n                file_name = self._generate_image_filename(parsed_url.path, f)\n                self._image_storage.save(file_name, f)\n        except Exception:\n            if file_name:\n                self._image_storage.delete(file_name)\n            raise serializers.ValidationError(\n                'Failed to download image {}'.format(url))\n\n        try:\n            self._validate_image(file_name)\n        except ValidationError as e:\n            self._image_storage.delete(file_name)\n            raise serializers.ValidationError(e)\n\n        return file_name\n\n    def _validate_image(self, file_name):\n        image_file = self._image_storage.open(file_name, 'rb')\n        image = ImageFile(image_file, file_name)\n        config = settings.IMAGE_ALIASES.get('*', {})\n        config.update(settings.IMAGE_ALIASES['tileimage'])\n        validate_image(image, **config)\n\n    @property\n    def _image_storage(self):\n        return project_models.Project._meta.get_field('tile_image').storage\n\n    @property\n    def _image_upload_to(self):\n        return project_models.Project._meta.get_field('tile_image').upload_to\n\n    def _generate_image_filename(self, url_path, file):\n        if callable(self._image_upload_to):\n            raise Exception('Callable upload_to fields are not supported')\n\n        root_path, extension = posixpath.splitext(url_path)\n        if file:\n            # Workaround: imghdr expects the files position on 0\n            file.seek(0)\n            extension = imghdr.what(file) or 'jpeg'\n\n        basename = posixpath.basename(root_path)\n        if not basename:\n            basename = 'bplan'\n\n        dirname = datetime.datetime.now().strftime(self._image_upload_to)\n        filename = posixpath.join(dirname, basename + '.' + extension)\n\n        return self._image_storage.get_available_name(filename)\n\n    def _send_project_created_signal(self, bplan):\n        a4dashboard_signals.project_created.send(\n            sender=self.__class__,\n            project=bplan,\n            user=self.context['request'].user\n        )\n\n    def _send_component_updated_signal(self, bplan):\n        component = components.projects['bplan']\n        a4dashboard_signals.project_component_updated.send(\n            sender=self.__class__,\n            project=bplan,\n            component=component,\n            user=self.context['request'].user\n        )\n", "path": "meinberlin/apps/bplan/serializers.py"}], "after_files": [{"content": "import datetime\nimport imghdr\nimport posixpath\nimport tempfile\nfrom urllib.parse import urlparse\n\nimport requests\nfrom django.apps import apps\nfrom django.conf import settings\nfrom django.contrib.sites.models import Site\nfrom django.core.exceptions import ValidationError\nfrom django.core.files.images import ImageFile\nfrom django.urls import reverse\nfrom django.utils import timezone\nfrom django.utils.translation import gettext as _\nfrom rest_framework import serializers\n\nfrom adhocracy4.dashboard import components\nfrom adhocracy4.dashboard import signals as a4dashboard_signals\nfrom adhocracy4.images.validators import validate_image\nfrom adhocracy4.modules import models as module_models\nfrom adhocracy4.phases import models as phase_models\nfrom adhocracy4.projects import models as project_models\n\nfrom .models import Bplan\nfrom .phases import StatementPhase\n\nBPLAN_EMBED = '<iframe height=\"500\" style=\"width: 100%; min-height: 300px; ' \\\n              'max-height: 100vh\" src=\"{}\" frameborder=\"0\"></iframe>'\nDOWNLOAD_IMAGE_SIZE_LIMIT_BYTES = 10 * 1024 * 1024\n\n\nclass BplanSerializer(serializers.ModelSerializer):\n    id = serializers.IntegerField(required=False)\n\n    # make write_only for consistency  reasons\n    start_date = serializers.DateTimeField(write_only=True)\n    end_date = serializers.DateTimeField(write_only=True)\n    image_url = serializers.URLField(\n        required=False,\n        write_only=True,\n    )\n    image_copyright = serializers.CharField(\n        required=False,\n        write_only=True,\n        source='tile_image_copyright',\n        allow_blank=True,\n        max_length=(project_models.Project._meta.\n                    get_field('tile_image_copyright').max_length),\n    )\n    embed_code = serializers.SerializerMethodField()\n\n    class Meta:\n        model = Bplan\n        fields = (\n            'id', 'name', 'identifier', 'description', 'url',\n            'office_worker_email', 'is_draft', 'start_date', 'end_date',\n            'image_url', 'image_copyright', 'embed_code'\n        )\n        extra_kwargs = {\n            # write_only for consistency reasons\n            'is_draft': {'default': False, 'write_only': True},\n            'name': {'write_only': True},\n            'description': {'write_only': True},\n            'url': {'write_only': True},\n            'office_worker_email': {'write_only': True},\n            'identifier': {'write_only': True}\n        }\n\n    def create(self, validated_data):\n        orga_pk = self._context.get('organisation_pk', None)\n        orga_model = apps.get_model(settings.A4_ORGANISATIONS_MODEL)\n        orga = orga_model.objects.get(pk=orga_pk)\n        validated_data['organisation'] = orga\n\n        start_date = validated_data['start_date']\n        end_date = validated_data['end_date']\n\n        image_url = validated_data.pop('image_url', None)\n        if image_url:\n            validated_data['tile_image'] = \\\n                self._download_image_from_url(image_url)\n\n        bplan = super().create(validated_data)\n        self._create_module_and_phase(bplan, start_date, end_date)\n        self._send_project_created_signal(bplan)\n        return bplan\n\n    def _create_module_and_phase(self, bplan, start_date, end_date):\n        module = module_models.Module.objects.create(\n            name=bplan.slug + '_module',\n            weight=1,\n            project=bplan,\n        )\n\n        phase_content = StatementPhase()\n        phase_models.Phase.objects.create(\n            name=_('Bplan statement phase'),\n            description=_('Bplan statement phase'),\n            type=phase_content.identifier,\n            module=module,\n            start_date=start_date,\n            end_date=end_date\n        )\n\n    def update(self, instance, validated_data):\n        start_date = validated_data.get('start_date', None)\n        end_date = validated_data.get('end_date', None)\n        if start_date or end_date:\n            self._update_phase(instance, start_date, end_date)\n            if end_date and end_date > timezone.localtime(timezone.now()):\n                instance.is_archived = False\n\n        image_url = validated_data.pop('image_url', None)\n        if image_url:\n            validated_data['tile_image'] = \\\n                self._download_image_from_url(image_url)\n\n        instance = super().update(instance, validated_data)\n\n        self._send_component_updated_signal(instance)\n        return instance\n\n    def _update_phase(self, bplan, start_date, end_date):\n        module = module_models.Module.objects.get(project=bplan)\n        phase = phase_models.Phase.objects.get(module=module)\n        if start_date:\n            phase.start_date = start_date\n        if end_date:\n            phase.end_date = end_date\n        phase.save()\n\n    def get_embed_code(self, bplan):\n        url = self._get_absolute_url(bplan)\n        embed = BPLAN_EMBED.format(url)\n        return embed\n\n    def _get_absolute_url(self, bplan):\n        site_url = Site.objects.get_current().domain\n        embed_url = reverse('embed-project', kwargs={'slug': bplan.slug, })\n        url = 'https://{}{}'.format(site_url, embed_url)\n        return url\n\n    def _download_image_from_url(self, url):\n        parsed_url = urlparse(url)\n        file_name = None\n        try:\n            r = requests.get(url, stream=True, timeout=10)\n            downloaded_bytes = 0\n            with tempfile.TemporaryFile() as f:\n                for chunk in r.iter_content(chunk_size=1024):\n                    downloaded_bytes += len(chunk)\n                    if downloaded_bytes > DOWNLOAD_IMAGE_SIZE_LIMIT_BYTES:\n                        raise serializers.ValidationError(\n                            'Image too large to download {}'.format(url))\n                    if chunk:\n                        f.write(chunk)\n                file_name = self._generate_image_filename(parsed_url.path, f)\n                self._image_storage.save(file_name, f)\n        except Exception:\n            if file_name:\n                self._image_storage.delete(file_name)\n            raise serializers.ValidationError(\n                'Failed to download image {}'.format(url))\n\n        try:\n            self._validate_image(file_name)\n        except ValidationError as e:\n            self._image_storage.delete(file_name)\n            raise serializers.ValidationError(e)\n\n        return file_name\n\n    def _validate_image(self, file_name):\n        image_file = self._image_storage.open(file_name, 'rb')\n        image = ImageFile(image_file, file_name)\n        config = settings.IMAGE_ALIASES.get('*', {})\n        config.update(settings.IMAGE_ALIASES['tileimage'])\n        validate_image(image, **config)\n\n    @property\n    def _image_storage(self):\n        return project_models.Project._meta.get_field('tile_image').storage\n\n    @property\n    def _image_upload_to(self):\n        return project_models.Project._meta.get_field('tile_image').upload_to\n\n    def _generate_image_filename(self, url_path, file):\n        if callable(self._image_upload_to):\n            raise Exception('Callable upload_to fields are not supported')\n\n        root_path, extension = posixpath.splitext(url_path)\n        if file:\n            # Workaround: imghdr expects the files position on 0\n            file.seek(0)\n            extension = imghdr.what(file) or 'jpeg'\n\n        basename = 'bplan_%s' % (timezone.now().strftime('%Y%m%dT%H%M%S'))\n\n        dirname = datetime.datetime.now().strftime(self._image_upload_to)\n        filename = posixpath.join(dirname, basename + '.' + extension)\n\n        return self._image_storage.get_available_name(filename)\n\n    def _send_project_created_signal(self, bplan):\n        a4dashboard_signals.project_created.send(\n            sender=self.__class__,\n            project=bplan,\n            user=self.context['request'].user\n        )\n\n    def _send_component_updated_signal(self, bplan):\n        component = components.projects['bplan']\n        a4dashboard_signals.project_component_updated.send(\n            sender=self.__class__,\n            project=bplan,\n            component=component,\n            user=self.context['request'].user\n        )\n", "path": "meinberlin/apps/bplan/serializers.py"}]}