We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Import from parent project list doesn't check permissions
### Description / Beschreibung

The list of projects in the import from parent project selection displays all parent projects independent of the user permissions.

### Expected behaviour / Erwartetes Verhalten

Import from parent project should display only projects where users have permissions to import data.

### Steps to reproduce / Schritte zum Reproduzieren

1. Load the dev fixtures in the latest RDMO Version: `python manage.py loaddata ../rdmo/testing/fixtures/*`
3. Log in to manager account.
4. Open Project "Child 1" or "Child 11"
5. List contains "Parent" although user does not have permissions.
6. When chosing "Parent" and trying to get to next step, RDMO displays "Forbidden", but I think it would be nicer if the option would not be present in the list.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `rdmo/projects/views/project.py`
Content:
```
1 import logging
2 
3 from django.contrib.auth.mixins import LoginRequiredMixin
4 from django.core.exceptions import PermissionDenied
5 from django.db import models
6 from django.db.models import F, OuterRef, Subquery
7 from django.forms import Form
8 from django.http import Http404
9 from django.shortcuts import get_object_or_404, redirect
10 from django.urls import reverse_lazy
11 from django.utils.decorators import method_decorator
12 from django.utils.translation import gettext_lazy as _
13 from django.views.decorators.csrf import ensure_csrf_cookie
14 from django.views.generic import DeleteView, DetailView, TemplateView
15 from django.views.generic.edit import FormMixin
16 from django_filters.views import FilterView
17 
18 from rdmo.accounts.utils import is_site_manager
19 from rdmo.core.plugins import get_plugin, get_plugins
20 from rdmo.core.views import ObjectPermissionMixin, RedirectViewMixin
21 from rdmo.questions.models import Catalog
22 from rdmo.questions.utils import get_widgets
23 from rdmo.tasks.models import Task
24 from rdmo.views.models import View
25 
26 from ..filters import ProjectFilter
27 from ..models import Integration, Invite, Membership, Project, Value
28 
29 logger = logging.getLogger(__name__)
30 
31 
32 class ProjectsView(LoginRequiredMixin, FilterView):
33     template_name = 'projects/projects.html'
34     context_object_name = 'projects'
35     paginate_by = 20
36     filterset_class = ProjectFilter
37 
38     def get_queryset(self):
39         # prepare projects queryset for this user
40         queryset = Project.objects.filter(user=self.request.user)
41         for instance in queryset:
42             queryset |= instance.get_descendants()
43         queryset = queryset.distinct()
44 
45         # prepare subquery for role
46         membership_subquery = models.Subquery(
47             Membership.objects.filter(project=models.OuterRef('pk'), user=self.request.user).values('role')
48         )
49         queryset = queryset.annotate(role=membership_subquery)
50 
51         # prepare subquery for last_changed
52         last_changed_subquery = models.Subquery(
53             Value.objects.filter(project=models.OuterRef('pk')).order_by('-updated').values('updated')[:1]
54         )
55         queryset = queryset.annotate(last_changed=models.functions.Greatest('updated', last_changed_subquery))
56 
57         # order by last changed
58         queryset = queryset.order_by('-last_changed')
59 
60         return queryset
61 
62     def get_context_data(self, **kwargs):
63         context = super(ProjectsView, self).get_context_data(**kwargs)
64         context['invites'] = Invite.objects.filter(user=self.request.user)
65         context['is_site_manager'] = is_site_manager(self.request.user)
66         return context
67 
68 
69 class SiteProjectsView(LoginRequiredMixin, FilterView):
70     template_name = 'projects/site_projects.html'
71     context_object_name = 'projects'
72     paginate_by = 20
73     filterset_class = ProjectFilter
74     model = Project
75 
76     def get_queryset(self):
77         if is_site_manager(self.request.user):
78             # prepare projects queryset for the site manager
79             queryset = Project.objects.filter_current_site()
80 
81             # prepare subquery for last_changed
82             last_changed_subquery = models.Subquery(
83                 Value.objects.filter(project=models.OuterRef('pk')).order_by('-updated').values('updated')[:1]
84             )
85             queryset = queryset.annotate(last_changed=models.functions.Greatest('updated', last_changed_subquery))
86 
87             return queryset
88         else:
89             raise PermissionDenied()
90 
91 
92 class ProjectDetailView(ObjectPermissionMixin, DetailView):
93     model = Project
94     queryset = Project.objects.prefetch_related(
95         'issues',
96         'issues__task',
97         'issues__task__conditions',
98         'issues__task__conditions__source',
99         'issues__task__conditions__target_option',
100         'tasks',
101         'views',
102         'values'
103     )
104     permission_required = 'projects.view_project_object'
105 
106     def get_context_data(self, **kwargs):
107         context = super(ProjectDetailView, self).get_context_data(**kwargs)
108         project = context['project']
109         ancestors = project.get_ancestors(include_self=True)
110         values = project.values.filter(snapshot=None).select_related('attribute', 'option')
111         highest = Membership.objects.filter(project__in=ancestors, user_id=OuterRef('user_id')).order_by('-project__level')
112         memberships = Membership.objects.filter(project__in=ancestors) \
113                                         .annotate(highest=Subquery(highest.values('project__level')[:1])) \
114                                         .filter(highest=F('project__level')) \
115                                         .select_related('user')
116 
117         integrations = Integration.objects.filter(project__in=ancestors)
118         context['catalogs'] = Catalog.objects.filter_current_site() \
119                                              .filter_group(self.request.user) \
120                                              .filter_availability(self.request.user)
121         context['tasks_available'] = Task.objects.filter_current_site() \
122                                                  .filter_catalog(self.object.catalog) \
123                                                  .filter_group(self.request.user) \
124                                                  .filter_availability(self.request.user).exists()
125         context['views_available'] = View.objects.filter_current_site() \
126                                                  .filter_catalog(self.object.catalog) \
127                                                  .filter_group(self.request.user) \
128                                                  .filter_availability(self.request.user).exists()
129         context['memberships'] = memberships.order_by('user__last_name', '-project__level')
130         context['integrations'] = integrations.order_by('provider_key', '-project__level')
131         context['providers'] = get_plugins('PROJECT_ISSUE_PROVIDERS')
132         context['issues'] = [issue for issue in project.issues.all() if issue.resolve(values)]
133         context['snapshots'] = project.snapshots.all()
134         context['invites'] = project.invites.all()
135         context['membership'] = Membership.objects.filter(project=project, user=self.request.user).first()
136 
137         return context
138 
139 
140 class ProjectDeleteView(ObjectPermissionMixin, RedirectViewMixin, DeleteView):
141     model = Project
142     queryset = Project.objects.all()
143     success_url = reverse_lazy('projects')
144     permission_required = 'projects.delete_project_object'
145 
146 
147 class ProjectJoinView(LoginRequiredMixin, RedirectViewMixin, TemplateView):
148     template_name = 'core/error.html'
149 
150     def get(self, request, token):
151         try:
152             invite = Invite.objects.get(token=token)
153 
154             if invite.is_expired:
155                 error = _('Sorry, your invitation has been expired.')
156                 invite.delete()
157             elif invite.user and invite.user != request.user:
158                 error = _('Sorry, but this invitation is for the user "%s".' % invite.user)
159             elif Membership.objects.filter(project=invite.project, user=request.user).exists():
160                 invite.delete()
161                 return redirect(invite.project.get_absolute_url())
162             else:
163                 Membership.objects.create(
164                     project=invite.project,
165                     user=request.user,
166                     role=invite.role
167                 )
168                 invite.delete()
169                 return redirect(invite.project.get_absolute_url())
170 
171         except Invite.DoesNotExist:
172             error = _('Sorry, the invitation link is not valid.')
173 
174         return self.render_to_response({
175             'title': _('Error'),
176             'errors': [error]
177         })
178 
179 
180 class ProjectCancelView(LoginRequiredMixin, RedirectViewMixin, TemplateView):
181     template_name = 'core/error.html'
182     success_url = reverse_lazy('projects')
183 
184     def get(self, request, token=None):
185         invite = get_object_or_404(Invite, token=token)
186         if invite.user in [None, request.user]:
187             invite.delete()
188 
189         return redirect(self.success_url)
190 
191 
192 class ProjectLeaveView(ObjectPermissionMixin, RedirectViewMixin, FormMixin, DetailView):
193     model = Project
194     form_class = Form
195     queryset = Project.objects.all()
196     success_url = reverse_lazy('projects')
197     permission_required = 'projects.leave_project_object'
198     template_name = 'projects/project_confirm_leave.html'
199 
200     def post(self, request, *args, **kwargs):
201         form = self.get_form()
202         if form.is_valid() and 'cancel' not in request.POST:
203             membership = Membership.objects.filter(project=self.get_object()).get(user=request.user)
204             if not membership.is_last_owner:
205                 membership.delete()
206 
207         return redirect(self.success_url)
208 
209 
210 class ProjectExportView(ObjectPermissionMixin, DetailView):
211     model = Project
212     queryset = Project.objects.all()
213     permission_required = 'projects.export_project_object'
214 
215     def get_export_plugin(self):
216         export_plugin = get_plugin('PROJECT_EXPORTS', self.kwargs.get('format'))
217         if export_plugin is None:
218             raise Http404
219 
220         export_plugin.request = self.request
221         export_plugin.project = self.object
222 
223         return export_plugin
224 
225     def get(self, request, *args, **kwargs):
226         self.object = self.get_object()
227         return self.get_export_plugin().render()
228 
229     def post(self, request, *args, **kwargs):
230         self.object = self.get_object()
231         return self.get_export_plugin().submit()
232 
233 
234 class ProjectQuestionsView(ObjectPermissionMixin, DetailView):
235     model = Project
236     queryset = Project.objects.all()
237     permission_required = 'projects.view_project_object'
238     template_name = 'projects/project_questions.html'
239 
240     @method_decorator(ensure_csrf_cookie)
241     def get(self, request, *args, **kwargs):
242         self.object = self.get_object()
243 
244         if self.object.catalog is None:
245             return redirect('project_error', pk=self.object.pk)
246         else:
247             context = self.get_context_data(object=self.object)
248             context['widgets'] = get_widgets()
249             return self.render_to_response(context)
250 
251 
252 class ProjectErrorView(ObjectPermissionMixin, DetailView):
253     model = Project
254     queryset = Project.objects.all()
255     permission_required = 'projects.view_project_object'
256     template_name = 'projects/project_error.html'
257
```
--- 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/rdmo/projects/views/project.py b/rdmo/projects/views/project.py
--- a/rdmo/projects/views/project.py
+++ b/rdmo/projects/views/project.py
@@ -126,6 +126,11 @@
                                                  .filter_catalog(self.object.catalog) \
                                                  .filter_group(self.request.user) \
                                                  .filter_availability(self.request.user).exists()
+        ancestors_import = []
+        for instance in ancestors.exclude(id=project.id):
+            if self.request.user.has_perm('projects.view_project_object', instance):
+                ancestors_import.append(instance)
+        context['ancestors_import'] = ancestors_import
         context['memberships'] = memberships.order_by('user__last_name', '-project__level')
         context['integrations'] = integrations.order_by('provider_key', '-project__level')
         context['providers'] = get_plugins('PROJECT_ISSUE_PROVIDERS')
@@ -133,7 +138,6 @@
         context['snapshots'] = project.snapshots.all()
         context['invites'] = project.invites.all()
         context['membership'] = Membership.objects.filter(project=project, user=self.request.user).first()
-
         return context

{"golden_diff": "diff --git a/rdmo/projects/views/project.py b/rdmo/projects/views/project.py\n--- a/rdmo/projects/views/project.py\n+++ b/rdmo/projects/views/project.py\n@@ -126,6 +126,11 @@\n                                                  .filter_catalog(self.object.catalog) \\\n                                                  .filter_group(self.request.user) \\\n                                                  .filter_availability(self.request.user).exists()\n+        ancestors_import = []\n+        for instance in ancestors.exclude(id=project.id):\n+            if self.request.user.has_perm('projects.view_project_object', instance):\n+                ancestors_import.append(instance)\n+        context['ancestors_import'] = ancestors_import\n         context['memberships'] = memberships.order_by('user__last_name', '-project__level')\n         context['integrations'] = integrations.order_by('provider_key', '-project__level')\n         context['providers'] = get_plugins('PROJECT_ISSUE_PROVIDERS')\n@@ -133,7 +138,6 @@\n         context['snapshots'] = project.snapshots.all()\n         context['invites'] = project.invites.all()\n         context['membership'] = Membership.objects.filter(project=project, user=self.request.user).first()\n-\n         return context\n", "issue": "Import from parent project list doesn't check permissions\n### Description / Beschreibung\r\n\r\nThe list of projects in the import from parent project selection displays all parent projects independent of the user permissions.\r\n\r\n### Expected behaviour / Erwartetes Verhalten\r\n\r\nImport from parent project should display only projects where users have permissions to import data.\r\n\r\n### Steps to reproduce / Schritte zum Reproduzieren\r\n\r\n1. Load the dev fixtures in the latest RDMO Version: `python manage.py loaddata ../rdmo/testing/fixtures/*`\r\n3. Log in to manager account.\r\n4. Open Project \"Child 1\" or \"Child 11\"\r\n5. List contains \"Parent\" although user does not have permissions.\r\n6. When chosing \"Parent\" and trying to get to next step, RDMO displays \"Forbidden\", but I think it would be nicer if the option would not be present in the list.\r\n\n", "before_files": [{"content": "import logging\n\nfrom django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.core.exceptions import PermissionDenied\nfrom django.db import models\nfrom django.db.models import F, OuterRef, Subquery\nfrom django.forms import Form\nfrom django.http import Http404\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.urls import reverse_lazy\nfrom django.utils.decorators import method_decorator\nfrom django.utils.translation import gettext_lazy as _\nfrom django.views.decorators.csrf import ensure_csrf_cookie\nfrom django.views.generic import DeleteView, DetailView, TemplateView\nfrom django.views.generic.edit import FormMixin\nfrom django_filters.views import FilterView\n\nfrom rdmo.accounts.utils import is_site_manager\nfrom rdmo.core.plugins import get_plugin, get_plugins\nfrom rdmo.core.views import ObjectPermissionMixin, RedirectViewMixin\nfrom rdmo.questions.models import Catalog\nfrom rdmo.questions.utils import get_widgets\nfrom rdmo.tasks.models import Task\nfrom rdmo.views.models import View\n\nfrom ..filters import ProjectFilter\nfrom ..models import Integration, Invite, Membership, Project, Value\n\nlogger = logging.getLogger(__name__)\n\n\nclass ProjectsView(LoginRequiredMixin, FilterView):\n    template_name = 'projects/projects.html'\n    context_object_name = 'projects'\n    paginate_by = 20\n    filterset_class = ProjectFilter\n\n    def get_queryset(self):\n        # prepare projects queryset for this user\n        queryset = Project.objects.filter(user=self.request.user)\n        for instance in queryset:\n            queryset |= instance.get_descendants()\n        queryset = queryset.distinct()\n\n        # prepare subquery for role\n        membership_subquery = models.Subquery(\n            Membership.objects.filter(project=models.OuterRef('pk'), user=self.request.user).values('role')\n        )\n        queryset = queryset.annotate(role=membership_subquery)\n\n        # prepare subquery for last_changed\n        last_changed_subquery = models.Subquery(\n            Value.objects.filter(project=models.OuterRef('pk')).order_by('-updated').values('updated')[:1]\n        )\n        queryset = queryset.annotate(last_changed=models.functions.Greatest('updated', last_changed_subquery))\n\n        # order by last changed\n        queryset = queryset.order_by('-last_changed')\n\n        return queryset\n\n    def get_context_data(self, **kwargs):\n        context = super(ProjectsView, self).get_context_data(**kwargs)\n        context['invites'] = Invite.objects.filter(user=self.request.user)\n        context['is_site_manager'] = is_site_manager(self.request.user)\n        return context\n\n\nclass SiteProjectsView(LoginRequiredMixin, FilterView):\n    template_name = 'projects/site_projects.html'\n    context_object_name = 'projects'\n    paginate_by = 20\n    filterset_class = ProjectFilter\n    model = Project\n\n    def get_queryset(self):\n        if is_site_manager(self.request.user):\n            # prepare projects queryset for the site manager\n            queryset = Project.objects.filter_current_site()\n\n            # prepare subquery for last_changed\n            last_changed_subquery = models.Subquery(\n                Value.objects.filter(project=models.OuterRef('pk')).order_by('-updated').values('updated')[:1]\n            )\n            queryset = queryset.annotate(last_changed=models.functions.Greatest('updated', last_changed_subquery))\n\n            return queryset\n        else:\n            raise PermissionDenied()\n\n\nclass ProjectDetailView(ObjectPermissionMixin, DetailView):\n    model = Project\n    queryset = Project.objects.prefetch_related(\n        'issues',\n        'issues__task',\n        'issues__task__conditions',\n        'issues__task__conditions__source',\n        'issues__task__conditions__target_option',\n        'tasks',\n        'views',\n        'values'\n    )\n    permission_required = 'projects.view_project_object'\n\n    def get_context_data(self, **kwargs):\n        context = super(ProjectDetailView, self).get_context_data(**kwargs)\n        project = context['project']\n        ancestors = project.get_ancestors(include_self=True)\n        values = project.values.filter(snapshot=None).select_related('attribute', 'option')\n        highest = Membership.objects.filter(project__in=ancestors, user_id=OuterRef('user_id')).order_by('-project__level')\n        memberships = Membership.objects.filter(project__in=ancestors) \\\n                                        .annotate(highest=Subquery(highest.values('project__level')[:1])) \\\n                                        .filter(highest=F('project__level')) \\\n                                        .select_related('user')\n\n        integrations = Integration.objects.filter(project__in=ancestors)\n        context['catalogs'] = Catalog.objects.filter_current_site() \\\n                                             .filter_group(self.request.user) \\\n                                             .filter_availability(self.request.user)\n        context['tasks_available'] = Task.objects.filter_current_site() \\\n                                                 .filter_catalog(self.object.catalog) \\\n                                                 .filter_group(self.request.user) \\\n                                                 .filter_availability(self.request.user).exists()\n        context['views_available'] = View.objects.filter_current_site() \\\n                                                 .filter_catalog(self.object.catalog) \\\n                                                 .filter_group(self.request.user) \\\n                                                 .filter_availability(self.request.user).exists()\n        context['memberships'] = memberships.order_by('user__last_name', '-project__level')\n        context['integrations'] = integrations.order_by('provider_key', '-project__level')\n        context['providers'] = get_plugins('PROJECT_ISSUE_PROVIDERS')\n        context['issues'] = [issue for issue in project.issues.all() if issue.resolve(values)]\n        context['snapshots'] = project.snapshots.all()\n        context['invites'] = project.invites.all()\n        context['membership'] = Membership.objects.filter(project=project, user=self.request.user).first()\n\n        return context\n\n\nclass ProjectDeleteView(ObjectPermissionMixin, RedirectViewMixin, DeleteView):\n    model = Project\n    queryset = Project.objects.all()\n    success_url = reverse_lazy('projects')\n    permission_required = 'projects.delete_project_object'\n\n\nclass ProjectJoinView(LoginRequiredMixin, RedirectViewMixin, TemplateView):\n    template_name = 'core/error.html'\n\n    def get(self, request, token):\n        try:\n            invite = Invite.objects.get(token=token)\n\n            if invite.is_expired:\n                error = _('Sorry, your invitation has been expired.')\n                invite.delete()\n            elif invite.user and invite.user != request.user:\n                error = _('Sorry, but this invitation is for the user \"%s\".' % invite.user)\n            elif Membership.objects.filter(project=invite.project, user=request.user).exists():\n                invite.delete()\n                return redirect(invite.project.get_absolute_url())\n            else:\n                Membership.objects.create(\n                    project=invite.project,\n                    user=request.user,\n                    role=invite.role\n                )\n                invite.delete()\n                return redirect(invite.project.get_absolute_url())\n\n        except Invite.DoesNotExist:\n            error = _('Sorry, the invitation link is not valid.')\n\n        return self.render_to_response({\n            'title': _('Error'),\n            'errors': [error]\n        })\n\n\nclass ProjectCancelView(LoginRequiredMixin, RedirectViewMixin, TemplateView):\n    template_name = 'core/error.html'\n    success_url = reverse_lazy('projects')\n\n    def get(self, request, token=None):\n        invite = get_object_or_404(Invite, token=token)\n        if invite.user in [None, request.user]:\n            invite.delete()\n\n        return redirect(self.success_url)\n\n\nclass ProjectLeaveView(ObjectPermissionMixin, RedirectViewMixin, FormMixin, DetailView):\n    model = Project\n    form_class = Form\n    queryset = Project.objects.all()\n    success_url = reverse_lazy('projects')\n    permission_required = 'projects.leave_project_object'\n    template_name = 'projects/project_confirm_leave.html'\n\n    def post(self, request, *args, **kwargs):\n        form = self.get_form()\n        if form.is_valid() and 'cancel' not in request.POST:\n            membership = Membership.objects.filter(project=self.get_object()).get(user=request.user)\n            if not membership.is_last_owner:\n                membership.delete()\n\n        return redirect(self.success_url)\n\n\nclass ProjectExportView(ObjectPermissionMixin, DetailView):\n    model = Project\n    queryset = Project.objects.all()\n    permission_required = 'projects.export_project_object'\n\n    def get_export_plugin(self):\n        export_plugin = get_plugin('PROJECT_EXPORTS', self.kwargs.get('format'))\n        if export_plugin is None:\n            raise Http404\n\n        export_plugin.request = self.request\n        export_plugin.project = self.object\n\n        return export_plugin\n\n    def get(self, request, *args, **kwargs):\n        self.object = self.get_object()\n        return self.get_export_plugin().render()\n\n    def post(self, request, *args, **kwargs):\n        self.object = self.get_object()\n        return self.get_export_plugin().submit()\n\n\nclass ProjectQuestionsView(ObjectPermissionMixin, DetailView):\n    model = Project\n    queryset = Project.objects.all()\n    permission_required = 'projects.view_project_object'\n    template_name = 'projects/project_questions.html'\n\n    @method_decorator(ensure_csrf_cookie)\n    def get(self, request, *args, **kwargs):\n        self.object = self.get_object()\n\n        if self.object.catalog is None:\n            return redirect('project_error', pk=self.object.pk)\n        else:\n            context = self.get_context_data(object=self.object)\n            context['widgets'] = get_widgets()\n            return self.render_to_response(context)\n\n\nclass ProjectErrorView(ObjectPermissionMixin, DetailView):\n    model = Project\n    queryset = Project.objects.all()\n    permission_required = 'projects.view_project_object'\n    template_name = 'projects/project_error.html'\n", "path": "rdmo/projects/views/project.py"}], "after_files": [{"content": "import logging\n\nfrom django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.core.exceptions import PermissionDenied\nfrom django.db import models\nfrom django.db.models import F, OuterRef, Subquery\nfrom django.forms import Form\nfrom django.http import Http404\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.urls import reverse_lazy\nfrom django.utils.decorators import method_decorator\nfrom django.utils.translation import gettext_lazy as _\nfrom django.views.decorators.csrf import ensure_csrf_cookie\nfrom django.views.generic import DeleteView, DetailView, TemplateView\nfrom django.views.generic.edit import FormMixin\nfrom django_filters.views import FilterView\n\nfrom rdmo.accounts.utils import is_site_manager\nfrom rdmo.core.plugins import get_plugin, get_plugins\nfrom rdmo.core.views import ObjectPermissionMixin, RedirectViewMixin\nfrom rdmo.questions.models import Catalog\nfrom rdmo.questions.utils import get_widgets\nfrom rdmo.tasks.models import Task\nfrom rdmo.views.models import View\n\nfrom ..filters import ProjectFilter\nfrom ..models import Integration, Invite, Membership, Project, Value\n\nlogger = logging.getLogger(__name__)\n\n\nclass ProjectsView(LoginRequiredMixin, FilterView):\n    template_name = 'projects/projects.html'\n    context_object_name = 'projects'\n    paginate_by = 20\n    filterset_class = ProjectFilter\n\n    def get_queryset(self):\n        # prepare projects queryset for this user\n        queryset = Project.objects.filter(user=self.request.user)\n        for instance in queryset:\n            queryset |= instance.get_descendants()\n        queryset = queryset.distinct()\n\n        # prepare subquery for role\n        membership_subquery = models.Subquery(\n            Membership.objects.filter(project=models.OuterRef('pk'), user=self.request.user).values('role')\n        )\n        queryset = queryset.annotate(role=membership_subquery)\n\n        # prepare subquery for last_changed\n        last_changed_subquery = models.Subquery(\n            Value.objects.filter(project=models.OuterRef('pk')).order_by('-updated').values('updated')[:1]\n        )\n        queryset = queryset.annotate(last_changed=models.functions.Greatest('updated', last_changed_subquery))\n\n        # order by last changed\n        queryset = queryset.order_by('-last_changed')\n\n        return queryset\n\n    def get_context_data(self, **kwargs):\n        context = super(ProjectsView, self).get_context_data(**kwargs)\n        context['invites'] = Invite.objects.filter(user=self.request.user)\n        context['is_site_manager'] = is_site_manager(self.request.user)\n        return context\n\n\nclass SiteProjectsView(LoginRequiredMixin, FilterView):\n    template_name = 'projects/site_projects.html'\n    context_object_name = 'projects'\n    paginate_by = 20\n    filterset_class = ProjectFilter\n    model = Project\n\n    def get_queryset(self):\n        if is_site_manager(self.request.user):\n            # prepare projects queryset for the site manager\n            queryset = Project.objects.filter_current_site()\n\n            # prepare subquery for last_changed\n            last_changed_subquery = models.Subquery(\n                Value.objects.filter(project=models.OuterRef('pk')).order_by('-updated').values('updated')[:1]\n            )\n            queryset = queryset.annotate(last_changed=models.functions.Greatest('updated', last_changed_subquery))\n\n            return queryset\n        else:\n            raise PermissionDenied()\n\n\nclass ProjectDetailView(ObjectPermissionMixin, DetailView):\n    model = Project\n    queryset = Project.objects.prefetch_related(\n        'issues',\n        'issues__task',\n        'issues__task__conditions',\n        'issues__task__conditions__source',\n        'issues__task__conditions__target_option',\n        'tasks',\n        'views',\n        'values'\n    )\n    permission_required = 'projects.view_project_object'\n\n    def get_context_data(self, **kwargs):\n        context = super(ProjectDetailView, self).get_context_data(**kwargs)\n        project = context['project']\n        ancestors = project.get_ancestors(include_self=True)\n        values = project.values.filter(snapshot=None).select_related('attribute', 'option')\n        highest = Membership.objects.filter(project__in=ancestors, user_id=OuterRef('user_id')).order_by('-project__level')\n        memberships = Membership.objects.filter(project__in=ancestors) \\\n                                        .annotate(highest=Subquery(highest.values('project__level')[:1])) \\\n                                        .filter(highest=F('project__level')) \\\n                                        .select_related('user')\n\n        integrations = Integration.objects.filter(project__in=ancestors)\n        context['catalogs'] = Catalog.objects.filter_current_site() \\\n                                             .filter_group(self.request.user) \\\n                                             .filter_availability(self.request.user)\n        context['tasks_available'] = Task.objects.filter_current_site() \\\n                                                 .filter_catalog(self.object.catalog) \\\n                                                 .filter_group(self.request.user) \\\n                                                 .filter_availability(self.request.user).exists()\n        context['views_available'] = View.objects.filter_current_site() \\\n                                                 .filter_catalog(self.object.catalog) \\\n                                                 .filter_group(self.request.user) \\\n                                                 .filter_availability(self.request.user).exists()\n        ancestors_import = []\n        for instance in ancestors.exclude(id=project.id):\n            if self.request.user.has_perm('projects.view_project_object', instance):\n                ancestors_import.append(instance)\n        context['ancestors_import'] = ancestors_import\n        context['memberships'] = memberships.order_by('user__last_name', '-project__level')\n        context['integrations'] = integrations.order_by('provider_key', '-project__level')\n        context['providers'] = get_plugins('PROJECT_ISSUE_PROVIDERS')\n        context['issues'] = [issue for issue in project.issues.all() if issue.resolve(values)]\n        context['snapshots'] = project.snapshots.all()\n        context['invites'] = project.invites.all()\n        context['membership'] = Membership.objects.filter(project=project, user=self.request.user).first()\n        return context\n\n\nclass ProjectDeleteView(ObjectPermissionMixin, RedirectViewMixin, DeleteView):\n    model = Project\n    queryset = Project.objects.all()\n    success_url = reverse_lazy('projects')\n    permission_required = 'projects.delete_project_object'\n\n\nclass ProjectJoinView(LoginRequiredMixin, RedirectViewMixin, TemplateView):\n    template_name = 'core/error.html'\n\n    def get(self, request, token):\n        try:\n            invite = Invite.objects.get(token=token)\n\n            if invite.is_expired:\n                error = _('Sorry, your invitation has been expired.')\n                invite.delete()\n            elif invite.user and invite.user != request.user:\n                error = _('Sorry, but this invitation is for the user \"%s\".' % invite.user)\n            elif Membership.objects.filter(project=invite.project, user=request.user).exists():\n                invite.delete()\n                return redirect(invite.project.get_absolute_url())\n            else:\n                Membership.objects.create(\n                    project=invite.project,\n                    user=request.user,\n                    role=invite.role\n                )\n                invite.delete()\n                return redirect(invite.project.get_absolute_url())\n\n        except Invite.DoesNotExist:\n            error = _('Sorry, the invitation link is not valid.')\n\n        return self.render_to_response({\n            'title': _('Error'),\n            'errors': [error]\n        })\n\n\nclass ProjectCancelView(LoginRequiredMixin, RedirectViewMixin, TemplateView):\n    template_name = 'core/error.html'\n    success_url = reverse_lazy('projects')\n\n    def get(self, request, token=None):\n        invite = get_object_or_404(Invite, token=token)\n        if invite.user in [None, request.user]:\n            invite.delete()\n\n        return redirect(self.success_url)\n\n\nclass ProjectLeaveView(ObjectPermissionMixin, RedirectViewMixin, FormMixin, DetailView):\n    model = Project\n    form_class = Form\n    queryset = Project.objects.all()\n    success_url = reverse_lazy('projects')\n    permission_required = 'projects.leave_project_object'\n    template_name = 'projects/project_confirm_leave.html'\n\n    def post(self, request, *args, **kwargs):\n        form = self.get_form()\n        if form.is_valid() and 'cancel' not in request.POST:\n            membership = Membership.objects.filter(project=self.get_object()).get(user=request.user)\n            if not membership.is_last_owner:\n                membership.delete()\n\n        return redirect(self.success_url)\n\n\nclass ProjectExportView(ObjectPermissionMixin, DetailView):\n    model = Project\n    queryset = Project.objects.all()\n    permission_required = 'projects.export_project_object'\n\n    def get_export_plugin(self):\n        export_plugin = get_plugin('PROJECT_EXPORTS', self.kwargs.get('format'))\n        if export_plugin is None:\n            raise Http404\n\n        export_plugin.request = self.request\n        export_plugin.project = self.object\n\n        return export_plugin\n\n    def get(self, request, *args, **kwargs):\n        self.object = self.get_object()\n        return self.get_export_plugin().render()\n\n    def post(self, request, *args, **kwargs):\n        self.object = self.get_object()\n        return self.get_export_plugin().submit()\n\n\nclass ProjectQuestionsView(ObjectPermissionMixin, DetailView):\n    model = Project\n    queryset = Project.objects.all()\n    permission_required = 'projects.view_project_object'\n    template_name = 'projects/project_questions.html'\n\n    @method_decorator(ensure_csrf_cookie)\n    def get(self, request, *args, **kwargs):\n        self.object = self.get_object()\n\n        if self.object.catalog is None:\n            return redirect('project_error', pk=self.object.pk)\n        else:\n            context = self.get_context_data(object=self.object)\n            context['widgets'] = get_widgets()\n            return self.render_to_response(context)\n\n\nclass ProjectErrorView(ObjectPermissionMixin, DetailView):\n    model = Project\n    queryset = Project.objects.all()\n    permission_required = 'projects.view_project_object'\n    template_name = 'projects/project_error.html'\n", "path": "rdmo/projects/views/project.py"}]}