Repository: liqd/a4-meinberlin

Issue:
timeline single project
if we only have one module running for one point in timeline, we should not show the tile but the module directly (see design). The tiles only make sense if you can chose between two questions.

![Bildschirmfoto 2019-07-05 um 12 37 25](https://user-images.githubusercontent.com/35491681/60717404-f71b2800-9f21-11e9-97ae-5ea6e41dbcd7.png)

design:
![Bildschirmfoto 2019-07-05 um 12 40 20](https://user-images.githubusercontent.com/35491681/60717459-20d44f00-9f22-11e9-959b-eac5e1b43850.png)




Discussion:

Resolving patch:
diff --git a/meinberlin/apps/contrib/mixins.py b/meinberlin/apps/contrib/mixins.py
--- a/meinberlin/apps/contrib/mixins.py
+++ b/meinberlin/apps/contrib/mixins.py
@@ -3,11 +3,11 @@
 from django.db.models import Min
 from django.db.models import Q
 from django.urls import resolve
+from django.utils import timezone
+from django.utils.functional import cached_property
 from django.utils.translation import ugettext_lazy as _
 from django.views import generic
 
-from adhocracy4.modules.models import Module
-
 RIGHT_OF_USE_LABEL = _('I hereby confirm that the copyrights for this '
                        'photo are with me or that I have received '
                        'rights of use from the author. I also confirm '
@@ -71,6 +71,8 @@ class ModuleClusterMixin:
 
     def _get_module_dict(self, count, start_date, end_date):
         return {
+            'title': _('{}. Online Participation').format(str(count)),
+            'type': 'module',
             'count': count,
             'date': start_date,
             'end_date': end_date,
@@ -116,33 +118,94 @@ def get_module_clusters(self, modules):
 class DisplayProjectOrModuleMixin(generic.base.ContextMixin,
                                   ModuleClusterMixin):
 
-    def module_clusters(self, modules):
-        return super().get_module_clusters(modules)
+    @cached_property
+    def module_clusters(self):
+        return super().get_module_clusters(self.modules)
 
-    @property
+    @cached_property
     def url_name(self):
         return resolve(self.request.path_info).url_name
 
-    @property
-    def other_modules(self):
-        modules = Module.objects.filter(project=self.project)\
+    @cached_property
+    def modules(self):
+        return self.project.modules\
             .annotate(start_date=Min('phase__start_date'))\
             .annotate(end_date=Max('phase__end_date'))\
+            .exclude(Q(start_date=None) | Q(end_date=None))\
             .order_by('start_date')
 
-        for cluster in self.module_clusters(modules):
+    @cached_property
+    def events(self):
+        return self.project.offlineevent_set.all()
+
+    @cached_property
+    def full_list(self):
+        module_cluster = self.module_clusters
+        event_list = self.get_events_list()
+        full_list = module_cluster + list(event_list)
+        return sorted(full_list, key=lambda k: k['date'])
+
+    @cached_property
+    def other_modules(self):
+        for cluster in self.module_clusters(self.modules):
             if self.module in cluster['modules']:
                 idx = cluster['modules'].index(self.module)
                 modules = cluster['modules']
                 return modules, idx
         return []
 
-    @property
+    @cached_property
     def extends(self):
         if self.url_name == 'module-detail':
             return 'a4modules/module_detail.html'
         return 'meinberlin_projects/project_detail.html'
 
+    @cached_property
+    def display_timeline(self):
+        return len(self.full_list) > 1
+
+    @cached_property
+    def initial_slide(self):
+        initial_slide = self.request.GET.get('initialSlide')
+        if initial_slide:
+            return int(initial_slide)
+        else:
+            now = timezone.now()
+            for idx, val in enumerate(self.full_list):
+                if 'type' in val and val['type'] == 'module':
+                    start_date = val['date']
+                    end_date = val['end_date']
+                    if start_date and end_date:
+                        if now >= start_date and now <= end_date:
+                            return idx
+        return 0
+
+    def get_current_event(self):
+        fl = self.full_list
+        idx = self.initial_slide
+        try:
+            current_dict = fl[idx]
+            if 'type' not in current_dict:
+                return self.full_list[self.initial_slide]
+        except (IndexError, KeyError):
+            return []
+        return []
+
+    def get_current_modules(self):
+        fl = self.full_list
+        idx = self.initial_slide
+        try:
+            current_dict = fl[idx]
+            if current_dict['type'] == 'module':
+                return self.full_list[self.initial_slide]['modules']
+        except (IndexError, KeyError):
+            return []
+
+    def get_events_list(self):
+        return self.events.values('date', 'name',
+                                  'event_type',
+                                  'slug', 'description')
+
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
         context['url_name'] = self.url_name
@@ -163,4 +226,9 @@ def get_context_data(self, **kwargs):
             context['index'] = idx + 1
             context['next'] = next_module
             context['previous'] = previous_module
+        else:
+            context['event'] = self.get_current_event()
+            context['modules'] = self.get_current_modules()
+            context['participation_dates'] = self.full_list
+            context['initial_slide'] = self.initial_slide
         return context
diff --git a/meinberlin/apps/projects/views.py b/meinberlin/apps/projects/views.py
--- a/meinberlin/apps/projects/views.py
+++ b/meinberlin/apps/projects/views.py
@@ -5,9 +5,6 @@
 from django.conf import settings
 from django.contrib import messages
 from django.contrib.auth import get_user_model
-from django.db.models import Max
-from django.db.models import Min
-from django.db.models import Q
 from django.shortcuts import get_object_or_404
 from django.shortcuts import redirect
 from django.utils import timezone
@@ -30,7 +27,7 @@
 from adhocracy4.projects import models as project_models
 from adhocracy4.projects.mixins import PhaseDispatchMixin
 from adhocracy4.projects.mixins import ProjectMixin
-from meinberlin.apps.contrib.mixins import ModuleClusterMixin
+from meinberlin.apps.contrib.mixins import DisplayProjectOrModuleMixin
 
 from . import forms
 from . import get_project_type
@@ -347,7 +344,7 @@ def get_permission_object(self):
 
 class ProjectDetailView(PermissionRequiredMixin,
                         generic.DetailView,
-                        ModuleClusterMixin):
+                        DisplayProjectOrModuleMixin):
 
     model = models.Project
     permission_required = 'a4projects.view_project'
@@ -366,17 +363,11 @@ def dispatch(self, request, *args, **kwargs):
 
         if self.modules.count() == 1 and not self.events:
             return self._view_by_phase()(request, *args, **kwargs)
+        elif len(self.get_current_modules()) == 1:
+            return self._view_by_phase()(request, *args, **kwargs)
         else:
             return super().dispatch(request)
 
-    def get_context_data(self, **kwargs):
-        context = super().get_context_data(**kwargs)
-        context['event'] = self.get_current_event()
-        context['modules'] = self.get_current_modules()
-        context['participation_dates'] = self.full_list
-        context['initial_slide'] = self.initial_slide
-        return context
-
     @cached_property
     def project(self):
         return self.get_object()
@@ -385,52 +376,8 @@ def project(self):
     def module(self):
         if self.modules.count() == 1 and not self.events:
             return self.modules.first()
-
-    @cached_property
-    def modules(self):
-        return self.project.modules\
-            .annotate(start_date=Min('phase__start_date'))\
-            .annotate(end_date=Max('phase__end_date'))\
-            .exclude(Q(start_date=None) | Q(end_date=None))\
-            .order_by('start_date', 'id')
-
-    @cached_property
-    def events(self):
-        return self.project.offlineevent_set.all()
-
-    @cached_property
-    def full_list(self):
-        module_cluster = self.module_clusters
-        event_list = self.get_events_list()
-        full_list = module_cluster + list(event_list)
-        return sorted(full_list, key=lambda k: k['date'])
-
-    @cached_property
-    def module_clusters(self):
-        clusters = super().get_module_clusters(self.modules)
-        if len(clusters) == 1:
-            clusters[0]['title'] = _('Online Participation')
-        return clusters
-
-    @cached_property
-    def initial_slide(self):
-        initial_slide = self.request.GET.get('initialSlide')
-        if initial_slide:
-            return int(initial_slide)
-        else:
-            now = timezone.now()
-            for idx, val in enumerate(self.full_list):
-                if 'type' in val and val['type'] == 'module':
-                    start_date = val['date']
-                    end_date = val['end_date']
-                    if start_date and end_date:
-                        if now >= start_date and now <= end_date:
-                            return idx
-        return 0
-
-    @cached_property
-    def display_timeline(self):
-        return len(self.full_list) > 1
+        elif len(self.get_current_modules()) == 1:
+            return self.modules.first()
 
     @cached_property
     def is_project_view(self):
@@ -444,41 +391,6 @@ def _view_by_phase(self):
         else:
             return super().dispatch
 
-    def _get_module_dict(self, count, start_date, end_date):
-        return {
-            'title': _('{}. Online Participation').format(str(count)),
-            'type': 'module',
-            'date': start_date,
-            'end_date': end_date,
-            'modules': []
-        }
-
-    def get_current_event(self):
-        fl = self.full_list
-        idx = self.initial_slide
-        try:
-            current_dict = fl[idx]
-            if 'type' not in current_dict:
-                return self.full_list[self.initial_slide]
-        except (IndexError, KeyError):
-            return []
-        return []
-
-    def get_current_modules(self):
-        fl = self.full_list
-        idx = self.initial_slide
-        try:
-            current_dict = fl[idx]
-            if current_dict['type'] == 'module':
-                return self.full_list[self.initial_slide]['modules']
-        except (IndexError, KeyError):
-            return []
-
-    def get_events_list(self):
-        return self.events.values('date', 'name',
-                                  'event_type',
-                                  'slug', 'description')
-
     @property
     def raise_exception(self):
         return self.request.user.is_authenticated


Test patch: