Repository: liqd/a4-meinberlin

Issue:
IndexError: list index out of range
https://sentry.liqd.net/sentry/meinberlin-prod/issues/828/

```
IndexError: list index out of range
(13 additional frame(s) were not displayed)
...
  File "rest_framework/serializers.py", line 526, in to_representation
    ret[field.field_name] = field.to_representation(attribute)
  File "rest_framework/fields.py", line 1873, in to_representation
    return method(value)
  File "meinberlin/apps/projects/serializers.py", line 231, in get_active_phase
    time_left = instance.time_left
  File "django/utils/functional.py", line 80, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
  File "adhocracy4/projects/models.py", line 433, in time_left
    best_unit = time_delta_list[0]

Internal Server Error: /projekte/
```


Discussion:
and here might be the same problem: https://github.com/liqd/a4-meinberlin/blob/24cad6d9eab91f4ba373a30a095dbd53d44e7334/meinberlin/apps/projects/serializers.py#L213
!

Resolving patch:
diff --git a/meinberlin/apps/plans/forms.py b/meinberlin/apps/plans/forms.py
--- a/meinberlin/apps/plans/forms.py
+++ b/meinberlin/apps/plans/forms.py
@@ -43,8 +43,9 @@ def __init__(self, *args, **kwargs):
 
     def save(self, commit=True):
         plan = super().save(commit=False)
-        group = plan._get_group(plan.creator, plan.organisation)
-        plan.group = group
+        if not plan.group:
+            group = plan._get_group(plan.creator, plan.organisation)
+            plan.group = group
         if commit:
             plan.save()
         return plan
diff --git a/meinberlin/apps/projects/serializers.py b/meinberlin/apps/projects/serializers.py
--- a/meinberlin/apps/projects/serializers.py
+++ b/meinberlin/apps/projects/serializers.py
@@ -212,7 +212,8 @@ def seconds_in_units(self, seconds):
         unit_limits = [
             ([_('day'), _('days')], 24 * 3600),
             ([_('hour'), _('hours')], 3600),
-            ([_('minute'), _('minutes')], 60)
+            ([_('minute'), _('minutes')], 60),
+            ([_('second'), _('seconds')], 1)
         ]
 
         for unit_name, limit in unit_limits:


Test patch:
diff --git a/tests/plans/test_dashboard_views.py b/tests/plans/test_dashboard_views.py
new file mode 100644
--- /dev/null
+++ b/tests/plans/test_dashboard_views.py
@@ -0,0 +1,155 @@
+import pytest
+from django.conf import settings
+from django.urls import reverse
+
+from adhocracy4.test.helpers import redirect_target
+from meinberlin.apps.plans.models import Plan
+from meinberlin.test.helpers import assert_template_response
+
+
+@pytest.mark.django_db
+def test_initiator_can_edit(client, plan_factory):
+    plan = plan_factory()
+    initiator = plan.organisation.initiators.first()
+    url = reverse('a4dashboard:plan-update',
+                  kwargs={'organisation_slug': plan.organisation.slug,
+                          'pk': plan.pk})
+    client.login(username=initiator.email, password='password')
+    response = client.get(url)
+    assert_template_response(
+        response, 'meinberlin_plans/plan_update_form.html')
+
+    choices = settings.A4_PROJECT_TOPICS
+    data = {
+        'title': 'my plan title',
+        'description_image': '',
+        'description_image_copyright': '',
+        'contact': 'me@example.com',
+        'point': '',
+        'point_label': '',
+        'district': '',
+        'cost': '1.000',
+        'description': 'this is a description',
+        'topics': choices[0][0],
+        'status': plan.status,
+        'participation': plan.participation
+    }
+    response = client.post(url, data)
+    assert redirect_target(response) == 'plan-list'
+    plan.refresh_from_db()
+    assert plan.topics == [data.get('topics')]
+    assert plan.title == data.get('title')
+    assert plan.description == data.get('description')
+
+
+@pytest.mark.django_db
+def test_group_member_can_edit(client, plan_factory, user_factory,
+                               group_factory, organisation):
+    group1 = group_factory()
+    group2 = group_factory()
+    group_member = user_factory.create(groups=(group1, group2))
+    organisation.groups.add(group2)
+    plan = plan_factory(group=group2, organisation=organisation)
+
+    url = reverse('a4dashboard:plan-update',
+                  kwargs={'organisation_slug': organisation.slug,
+                          'pk': plan.pk})
+    client.login(username=group_member.email, password='password')
+    response = client.get(url)
+    assert_template_response(
+        response, 'meinberlin_plans/plan_update_form.html')
+
+    choices = settings.A4_PROJECT_TOPICS
+    data = {
+        'title': 'my plan title',
+        'description_image': '',
+        'description_image_copyright': '',
+        'contact': 'me@example.com',
+        'point': '',
+        'point_label': '',
+        'district': '',
+        'cost': '1.000',
+        'description': 'this is a description',
+        'topics': choices[0][0],
+        'status': plan.status,
+        'participation': plan.participation
+    }
+    response = client.post(url, data)
+    assert redirect_target(response) == 'plan-list'
+    plan.refresh_from_db()
+    assert plan.topics == [data.get('topics')]
+    assert plan.title == data.get('title')
+    assert plan.description == data.get('description')
+    assert plan.group == group2
+
+
+@pytest.mark.django_db
+def test_initiator_can_create(client, organisation):
+    initiator = organisation.initiators.first()
+    url = reverse('a4dashboard:plan-create',
+                  kwargs={'organisation_slug': organisation.slug})
+    client.login(username=initiator.email, password='password')
+    response = client.get(url)
+    assert_template_response(
+        response, 'meinberlin_plans/plan_create_dashboard.html')
+
+    choices = settings.A4_PROJECT_TOPICS
+    data = {
+        'title': 'my plan title',
+        'description_image': '',
+        'description_image_copyright': '',
+        'contact': 'me@example.com',
+        'point': '',
+        'point_label': '',
+        'district': '',
+        'cost': '1.000',
+        'description': 'this is a description',
+        'topics': choices[0][0],
+        'status': 0,
+        'participation': 2
+    }
+    response = client.post(url, data)
+    assert redirect_target(response) == 'plan-list'
+    plan = Plan.objects.all().first()
+    assert plan.topics == [data.get('topics')]
+    assert plan.title == data.get('title')
+    assert plan.description == data.get('description')
+    assert not plan.group
+
+
+@pytest.mark.django_db
+def test_group_member_can_create(client, organisation, user_factory,
+                                 group_factory):
+    group1 = group_factory()
+    group2 = group_factory()
+    group_member = user_factory.create(groups=(group1, group2))
+    organisation.groups.add(group2)
+    url = reverse('a4dashboard:plan-create',
+                  kwargs={'organisation_slug': organisation.slug})
+    client.login(username=group_member.email, password='password')
+    response = client.get(url)
+    assert_template_response(
+        response, 'meinberlin_plans/plan_create_dashboard.html')
+
+    choices = settings.A4_PROJECT_TOPICS
+    data = {
+        'title': 'my plan title',
+        'description_image': '',
+        'description_image_copyright': '',
+        'contact': 'me@example.com',
+        'point': '',
+        'point_label': '',
+        'district': '',
+        'cost': '1.000',
+        'description': 'this is a description',
+        'topics': choices[0][0],
+        'status': 0,
+        'participation': 2
+    }
+    response = client.post(url, data)
+    assert redirect_target(response) == 'plan-list'
+    plan = Plan.objects.all().first()
+    assert plan.topics == [data.get('topics')]
+    assert plan.title == data.get('title')
+    assert plan.description == data.get('description')
+    assert plan.group == group2