We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
HTTP 500 instead of 404 on unknown category
* open https://apps.nextcloud.com/category/auth
* actual: get a 500 and a log entry
* expected: get a 404

Log line:

```
Internal Server Error: /categories/auth
Traceback (most recent call last):
  File "/opt/appstore/venv/lib/python3.5/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/opt/appstore/venv/lib/python3.5/site-packages/django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/opt/appstore/venv/lib/python3.5/site-packages/django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/opt/appstore/venv/lib/python3.5/site-packages/django/views/generic/base.py", line 71, in view
    return self.dispatch(request, *args, **kwargs)
  File "/opt/appstore/venv/lib/python3.5/site-packages/django/views/generic/base.py", line 97, in dispatch
    return handler(request, *args, **kwargs)
  File "/opt/appstore/venv/lib/python3.5/site-packages/django/views/generic/list.py", line 157, in get
    context = self.get_context_data()
  File "/opt/appstore/nextcloudappstore/core/views.py", line 222, in get_context_data
    context['current_category'] = Category.objects.get(id=category_id)
  File "/opt/appstore/venv/lib/python3.5/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/opt/appstore/venv/lib/python3.5/site-packages/django/db/models/query.py", line 408, in get
    self.model._meta.object_name
nextcloudappstore.core.models.Category.DoesNotExist: Category matching query does not exist.
```

cc @mario 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `nextcloudappstore/core/views.py`
Content:
```
1 from urllib.parse import urlencode
2 
3 from django.conf import settings
4 from django.contrib.auth.mixins import LoginRequiredMixin
5 from django.contrib.auth.models import User
6 from django.core.exceptions import ObjectDoesNotExist
7 from django.db.models import Q
8 from django.http import HttpResponse
9 from django.shortcuts import get_object_or_404, redirect
10 from django.utils.functional import cached_property
11 from django.utils.translation import get_language, get_language_info
12 from django.views.decorators.http import etag
13 from django.views.generic.base import TemplateView
14 from django.views.generic.detail import DetailView
15 from django.views.generic.list import ListView
16 from rest_framework.generics import ListAPIView
17 from semantic_version import Version
18 
19 from nextcloudappstore.core.caching import app_etag
20 from nextcloudappstore.core.facades import flatmap
21 from nextcloudappstore.core.forms import AppRatingForm, AppReleaseUploadForm, \
22     AppRegisterForm
23 from nextcloudappstore.core.models import App, Category, AppRating
24 from nextcloudappstore.core.serializers import AppRatingSerializer
25 from nextcloudappstore.core.versioning import pad_min_version
26 
27 
28 @etag(app_etag)
29 def app_description(request, id):
30     app = get_object_or_404(App, id=id)
31     return HttpResponse(app.description, content_type='text/plain')
32 
33 
34 class AppRatingApi(ListAPIView):
35     serializer_class = AppRatingSerializer
36 
37     def get_queryset(self):
38         id = self.kwargs.get('id')
39         lang = self.request.GET.get('lang', self.request.LANGUAGE_CODE)
40         app = get_object_or_404(App, id=id)
41         queryset = AppRating.objects.language(lang).filter(app=app)
42 
43         current_user = self.request.GET.get('current_user', 'false')
44         if current_user == 'true':
45             return queryset.filter(user=self.request.user)
46         else:
47             return queryset
48 
49 
50 class AppDetailView(DetailView):
51     queryset = App.objects.prefetch_related(
52         'releases',
53         'screenshots',
54         'co_maintainers',
55         'translations',
56     ).select_related('owner')
57     template_name = 'app/detail.html'
58     slug_field = 'id'
59     slug_url_kwarg = 'id'
60 
61     def post(self, request, id):
62         form = AppRatingForm(request.POST, id=id, user=request.user)
63         # there is no way that a rating can be invalid by default
64         if form.is_valid() and request.user.is_authenticated:
65             form.save()
66         return redirect('app-detail', id=id)
67 
68     def get_context_data(self, **kwargs):
69         context = super().get_context_data(**kwargs)
70 
71         context['DISCOURSE_URL'] = settings.DISCOURSE_URL.rstrip('/')
72         context['rating_form'] = AppRatingForm(
73             initial={'language_code': get_language()})
74 
75         ratings = AppRating.objects.filter(app=context['app'])
76         rating_languages = flatmap(
77             lambda r: r.get_available_languages(), ratings)
78 
79         # make sure current session language is in the list even if there are
80         # no comments.
81         rating_languages = list(rating_languages)
82         if get_language() not in rating_languages:
83             rating_languages.append(get_language())
84 
85         context['languages'] = set(sorted(rating_languages))
86         context['fallbackLang'] = 'en' if 'en' in context['languages'] else ''
87         context['user_has_rated_app'] = False
88         if self.request.user.is_authenticated:
89             try:
90                 app_rating = AppRating.objects.get(user=self.request.user,
91                                                    app=context['app'])
92 
93                 # if parler falls back to a fallback language
94                 # it doesn't set the language as current language
95                 # and we can't select the correct language in the
96                 # frontend. So we try and find a languge that is
97                 # available
98                 language_code = app_rating.get_current_language()
99                 if not app_rating.has_translation(language_code):
100                     for fallback in app_rating.get_fallback_languages():
101                         if app_rating.has_translation(fallback):
102                             app_rating.set_current_language(fallback)
103 
104                 # when accessing an empty comment django-parler tries to
105                 # fall back to the default language. However for comments
106                 # the default (English) does not always exist. Unfortunately
107                 # it throws the same exception as non existing models,
108                 # so we need to access it beforehand
109                 try:
110                     comment = app_rating.comment
111                 except AppRating.DoesNotExist:
112                     comment = ''
113 
114                 context['rating_form'] = AppRatingForm({
115                     'rating': app_rating.rating,
116                     'comment': comment,
117                     'language_code': app_rating.get_current_language(),
118                 })
119                 context['user_has_rated_app'] = True
120             except AppRating.DoesNotExist:
121                 pass
122         context['categories'] = Category.objects.prefetch_related(
123             'translations').all()
124         context['latest_releases_by_platform_v'] = \
125             self.object.latest_releases_by_platform_v()
126         context['is_integration'] = self.object.is_integration
127         return context
128 
129 
130 class AppReleasesView(DetailView):
131     queryset = App.objects.prefetch_related(
132         'translations',
133         'releases__translations',
134         'releases__phpextensiondependencies__php_extension',
135         'releases__databasedependencies__database',
136         'releases__shell_commands',
137         'releases__licenses',
138     )
139     template_name = 'app/releases.html'
140     slug_field = 'id'
141     slug_url_kwarg = 'id'
142 
143     def get_context_data(self, **kwargs):
144         context = super().get_context_data(**kwargs)
145         context['categories'] = Category.objects.prefetch_related(
146             'translations').all()
147 
148         releases = self.object.releases_by_platform_v()
149         unstables = self.object.unstable_releases_by_platform_v()
150         versions = set(list(releases.keys()) + list(unstables.keys()))
151         all_releases = list(map(
152             lambda v: (v, releases.get(v, []) + unstables.get(v, [])),
153             versions))
154         context['releases_by_platform_v'] = \
155             self._sort_by_platform_v(all_releases)
156         return context
157 
158     def _sort_by_platform_v(self, releases_by_platform, reverse=True):
159         """Sorts a list of tuples like (<platform version>, [releases]) by
160         platform version.
161 
162         :param releases_by_platform: A list of tuples.
163         :param reverse: Descending order if True, ascending otherwise.
164         :return sorted list of tuples.
165         """
166 
167         return sorted(releases_by_platform, reverse=reverse,
168                       key=lambda v: Version(pad_min_version(v[0])))
169 
170 
171 class CategoryAppListView(ListView):
172     model = App
173     template_name = 'app/list.html'
174     allow_empty = True
175 
176     def get_queryset(self):
177         order_by = self.request.GET.get('order_by', 'rating_overall')
178         ordering = self.request.GET.get('ordering', 'desc')
179         is_featured = self.request.GET.get('is_featured', False)
180         maintainer = self.request.GET.get('maintainer', False)
181         sort_columns = []
182 
183         if self.kwargs.get('is_featured_category', False):
184             is_featured = "true"
185 
186         allowed_order_by = {'name', 'last_release', 'rating_overall',
187                             'rating_recent'}
188         if order_by in allowed_order_by:
189             if order_by == 'name':
190                 order_by = 'translations__name'
191             if ordering == 'desc':
192                 sort_columns.append('-' + order_by)
193             else:
194                 sort_columns.append(order_by)
195 
196         lang = get_language_info(get_language())['code']
197         category_id = self.kwargs['id']
198         queryset = App.objects.search(self.search_terms, lang).order_by(
199             *sort_columns).filter(Q(releases__gt=0) | (Q(is_integration=True) &
200                                                        Q(approved=True)))
201         if maintainer:
202             try:
203                 user = User.objects.get_by_natural_key(maintainer)
204                 queryset = queryset.filter(Q(owner=user) |
205                                            Q(co_maintainers=user))
206             except ObjectDoesNotExist:
207                 return queryset.none()
208         if category_id:
209             queryset = queryset.filter(categories__id=category_id)
210         if is_featured == "true":
211             queryset = queryset.filter(is_featured=True)
212         return queryset.prefetch_related('screenshots', 'translations')
213 
214     def get_context_data(self, **kwargs):
215         context = super().get_context_data(**kwargs)
216         context['categories'] = Category.objects.prefetch_related(
217             'translations').all()
218         category_id = self.kwargs['id']
219         context['is_featured_category'] = self.kwargs \
220             .get('is_featured_category', False)
221         if category_id:
222             context['current_category'] = Category.objects.get(id=category_id)
223         if self.search_terms:
224             context['search_query'] = ' '.join(self.search_terms)
225         context['url_params'] = self.url_params
226         return context
227 
228     @cached_property
229     def url_params(self):
230         """URL encoded strings with the GET params of the last request.
231 
232         Intended for preserving GET params upon clicking a link by including
233         one (and only one) of these strings in the "href" attribute.
234 
235         The parameters are divided into three groups: search, filters and
236         ordering. In addition to these three, the returned dict also contains
237         some combinations of them, as specified by the dict keys.
238 
239         No leading "?" or "&".
240 
241         :return dict with URL encoded strings.
242         """
243 
244         search = self._url_params_str('search')
245         filters = self._url_params_str('is_featured', 'maintainer')
246         ordering = self._url_params_str('order_by', 'ordering')
247 
248         return {
249             'search': search,
250             'filters': filters,
251             'ordering': ordering,
252             'search_filters': self._join_url_params_strs(search, filters),
253             'filters_ordering': self._join_url_params_strs(filters, ordering),
254         }
255 
256     def _url_params_str(self, *params):
257         args = map(lambda param: (param, self.request.GET.get(param, '')),
258                    params)
259         present_args = filter(lambda a: a[1], args)
260         return urlencode(dict(present_args))
261 
262     def _join_url_params_strs(self, *strings):
263         return '&'.join(filter(None, strings))
264 
265     @cached_property
266     def search_terms(self):
267         return self.request.GET.get('search', '').strip().split()
268 
269 
270 class AppUploadView(LoginRequiredMixin, TemplateView):
271     template_name = 'app/upload.html'
272 
273     def get_context_data(self, **kwargs):
274         context = super().get_context_data(**kwargs)
275         context['form'] = AppReleaseUploadForm()
276         return context
277 
278 
279 class AppRegisterView(LoginRequiredMixin, TemplateView):
280     template_name = 'app/register.html'
281 
282     def get_context_data(self, **kwargs):
283         context = super().get_context_data(**kwargs)
284         context['form'] = AppRegisterForm()
285         return context
286
```
--- 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/nextcloudappstore/core/views.py b/nextcloudappstore/core/views.py
--- a/nextcloudappstore/core/views.py
+++ b/nextcloudappstore/core/views.py
@@ -219,7 +219,8 @@
         context['is_featured_category'] = self.kwargs \
             .get('is_featured_category', False)
         if category_id:
-            context['current_category'] = Category.objects.get(id=category_id)
+            context['current_category'] = get_object_or_404(Category,
+                                                            id=category_id)
         if self.search_terms:
             context['search_query'] = ' '.join(self.search_terms)
         context['url_params'] = self.url_params

{"golden_diff": "diff --git a/nextcloudappstore/core/views.py b/nextcloudappstore/core/views.py\n--- a/nextcloudappstore/core/views.py\n+++ b/nextcloudappstore/core/views.py\n@@ -219,7 +219,8 @@\n         context['is_featured_category'] = self.kwargs \\\n             .get('is_featured_category', False)\n         if category_id:\n-            context['current_category'] = Category.objects.get(id=category_id)\n+            context['current_category'] = get_object_or_404(Category,\n+                                                            id=category_id)\n         if self.search_terms:\n             context['search_query'] = ' '.join(self.search_terms)\n         context['url_params'] = self.url_params\n", "issue": "HTTP 500 instead of 404 on unknown category\n* open https://apps.nextcloud.com/category/auth\r\n* actual: get a 500 and a log entry\r\n* expected: get a 404\r\n\r\nLog line:\r\n\r\n```\r\nInternal Server Error: /categories/auth\r\nTraceback (most recent call last):\r\n  File \"/opt/appstore/venv/lib/python3.5/site-packages/django/core/handlers/exception.py\", line 34, in inner\r\n    response = get_response(request)\r\n  File \"/opt/appstore/venv/lib/python3.5/site-packages/django/core/handlers/base.py\", line 115, in _get_response\r\n    response = self.process_exception_by_middleware(e, request)\r\n  File \"/opt/appstore/venv/lib/python3.5/site-packages/django/core/handlers/base.py\", line 113, in _get_response\r\n    response = wrapped_callback(request, *callback_args, **callback_kwargs)\r\n  File \"/opt/appstore/venv/lib/python3.5/site-packages/django/views/generic/base.py\", line 71, in view\r\n    return self.dispatch(request, *args, **kwargs)\r\n  File \"/opt/appstore/venv/lib/python3.5/site-packages/django/views/generic/base.py\", line 97, in dispatch\r\n    return handler(request, *args, **kwargs)\r\n  File \"/opt/appstore/venv/lib/python3.5/site-packages/django/views/generic/list.py\", line 157, in get\r\n    context = self.get_context_data()\r\n  File \"/opt/appstore/nextcloudappstore/core/views.py\", line 222, in get_context_data\r\n    context['current_category'] = Category.objects.get(id=category_id)\r\n  File \"/opt/appstore/venv/lib/python3.5/site-packages/django/db/models/manager.py\", line 82, in manager_method\r\n    return getattr(self.get_queryset(), name)(*args, **kwargs)\r\n  File \"/opt/appstore/venv/lib/python3.5/site-packages/django/db/models/query.py\", line 408, in get\r\n    self.model._meta.object_name\r\nnextcloudappstore.core.models.Category.DoesNotExist: Category matching query does not exist.\r\n```\r\n\r\ncc @mario \n", "before_files": [{"content": "from urllib.parse import urlencode\n\nfrom django.conf import settings\nfrom django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.contrib.auth.models import User\nfrom django.core.exceptions import ObjectDoesNotExist\nfrom django.db.models import Q\nfrom django.http import HttpResponse\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import get_language, get_language_info\nfrom django.views.decorators.http import etag\nfrom django.views.generic.base import TemplateView\nfrom django.views.generic.detail import DetailView\nfrom django.views.generic.list import ListView\nfrom rest_framework.generics import ListAPIView\nfrom semantic_version import Version\n\nfrom nextcloudappstore.core.caching import app_etag\nfrom nextcloudappstore.core.facades import flatmap\nfrom nextcloudappstore.core.forms import AppRatingForm, AppReleaseUploadForm, \\\n    AppRegisterForm\nfrom nextcloudappstore.core.models import App, Category, AppRating\nfrom nextcloudappstore.core.serializers import AppRatingSerializer\nfrom nextcloudappstore.core.versioning import pad_min_version\n\n\n@etag(app_etag)\ndef app_description(request, id):\n    app = get_object_or_404(App, id=id)\n    return HttpResponse(app.description, content_type='text/plain')\n\n\nclass AppRatingApi(ListAPIView):\n    serializer_class = AppRatingSerializer\n\n    def get_queryset(self):\n        id = self.kwargs.get('id')\n        lang = self.request.GET.get('lang', self.request.LANGUAGE_CODE)\n        app = get_object_or_404(App, id=id)\n        queryset = AppRating.objects.language(lang).filter(app=app)\n\n        current_user = self.request.GET.get('current_user', 'false')\n        if current_user == 'true':\n            return queryset.filter(user=self.request.user)\n        else:\n            return queryset\n\n\nclass AppDetailView(DetailView):\n    queryset = App.objects.prefetch_related(\n        'releases',\n        'screenshots',\n        'co_maintainers',\n        'translations',\n    ).select_related('owner')\n    template_name = 'app/detail.html'\n    slug_field = 'id'\n    slug_url_kwarg = 'id'\n\n    def post(self, request, id):\n        form = AppRatingForm(request.POST, id=id, user=request.user)\n        # there is no way that a rating can be invalid by default\n        if form.is_valid() and request.user.is_authenticated:\n            form.save()\n        return redirect('app-detail', id=id)\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n\n        context['DISCOURSE_URL'] = settings.DISCOURSE_URL.rstrip('/')\n        context['rating_form'] = AppRatingForm(\n            initial={'language_code': get_language()})\n\n        ratings = AppRating.objects.filter(app=context['app'])\n        rating_languages = flatmap(\n            lambda r: r.get_available_languages(), ratings)\n\n        # make sure current session language is in the list even if there are\n        # no comments.\n        rating_languages = list(rating_languages)\n        if get_language() not in rating_languages:\n            rating_languages.append(get_language())\n\n        context['languages'] = set(sorted(rating_languages))\n        context['fallbackLang'] = 'en' if 'en' in context['languages'] else ''\n        context['user_has_rated_app'] = False\n        if self.request.user.is_authenticated:\n            try:\n                app_rating = AppRating.objects.get(user=self.request.user,\n                                                   app=context['app'])\n\n                # if parler falls back to a fallback language\n                # it doesn't set the language as current language\n                # and we can't select the correct language in the\n                # frontend. So we try and find a languge that is\n                # available\n                language_code = app_rating.get_current_language()\n                if not app_rating.has_translation(language_code):\n                    for fallback in app_rating.get_fallback_languages():\n                        if app_rating.has_translation(fallback):\n                            app_rating.set_current_language(fallback)\n\n                # when accessing an empty comment django-parler tries to\n                # fall back to the default language. However for comments\n                # the default (English) does not always exist. Unfortunately\n                # it throws the same exception as non existing models,\n                # so we need to access it beforehand\n                try:\n                    comment = app_rating.comment\n                except AppRating.DoesNotExist:\n                    comment = ''\n\n                context['rating_form'] = AppRatingForm({\n                    'rating': app_rating.rating,\n                    'comment': comment,\n                    'language_code': app_rating.get_current_language(),\n                })\n                context['user_has_rated_app'] = True\n            except AppRating.DoesNotExist:\n                pass\n        context['categories'] = Category.objects.prefetch_related(\n            'translations').all()\n        context['latest_releases_by_platform_v'] = \\\n            self.object.latest_releases_by_platform_v()\n        context['is_integration'] = self.object.is_integration\n        return context\n\n\nclass AppReleasesView(DetailView):\n    queryset = App.objects.prefetch_related(\n        'translations',\n        'releases__translations',\n        'releases__phpextensiondependencies__php_extension',\n        'releases__databasedependencies__database',\n        'releases__shell_commands',\n        'releases__licenses',\n    )\n    template_name = 'app/releases.html'\n    slug_field = 'id'\n    slug_url_kwarg = 'id'\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context['categories'] = Category.objects.prefetch_related(\n            'translations').all()\n\n        releases = self.object.releases_by_platform_v()\n        unstables = self.object.unstable_releases_by_platform_v()\n        versions = set(list(releases.keys()) + list(unstables.keys()))\n        all_releases = list(map(\n            lambda v: (v, releases.get(v, []) + unstables.get(v, [])),\n            versions))\n        context['releases_by_platform_v'] = \\\n            self._sort_by_platform_v(all_releases)\n        return context\n\n    def _sort_by_platform_v(self, releases_by_platform, reverse=True):\n        \"\"\"Sorts a list of tuples like (<platform version>, [releases]) by\n        platform version.\n\n        :param releases_by_platform: A list of tuples.\n        :param reverse: Descending order if True, ascending otherwise.\n        :return sorted list of tuples.\n        \"\"\"\n\n        return sorted(releases_by_platform, reverse=reverse,\n                      key=lambda v: Version(pad_min_version(v[0])))\n\n\nclass CategoryAppListView(ListView):\n    model = App\n    template_name = 'app/list.html'\n    allow_empty = True\n\n    def get_queryset(self):\n        order_by = self.request.GET.get('order_by', 'rating_overall')\n        ordering = self.request.GET.get('ordering', 'desc')\n        is_featured = self.request.GET.get('is_featured', False)\n        maintainer = self.request.GET.get('maintainer', False)\n        sort_columns = []\n\n        if self.kwargs.get('is_featured_category', False):\n            is_featured = \"true\"\n\n        allowed_order_by = {'name', 'last_release', 'rating_overall',\n                            'rating_recent'}\n        if order_by in allowed_order_by:\n            if order_by == 'name':\n                order_by = 'translations__name'\n            if ordering == 'desc':\n                sort_columns.append('-' + order_by)\n            else:\n                sort_columns.append(order_by)\n\n        lang = get_language_info(get_language())['code']\n        category_id = self.kwargs['id']\n        queryset = App.objects.search(self.search_terms, lang).order_by(\n            *sort_columns).filter(Q(releases__gt=0) | (Q(is_integration=True) &\n                                                       Q(approved=True)))\n        if maintainer:\n            try:\n                user = User.objects.get_by_natural_key(maintainer)\n                queryset = queryset.filter(Q(owner=user) |\n                                           Q(co_maintainers=user))\n            except ObjectDoesNotExist:\n                return queryset.none()\n        if category_id:\n            queryset = queryset.filter(categories__id=category_id)\n        if is_featured == \"true\":\n            queryset = queryset.filter(is_featured=True)\n        return queryset.prefetch_related('screenshots', 'translations')\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context['categories'] = Category.objects.prefetch_related(\n            'translations').all()\n        category_id = self.kwargs['id']\n        context['is_featured_category'] = self.kwargs \\\n            .get('is_featured_category', False)\n        if category_id:\n            context['current_category'] = Category.objects.get(id=category_id)\n        if self.search_terms:\n            context['search_query'] = ' '.join(self.search_terms)\n        context['url_params'] = self.url_params\n        return context\n\n    @cached_property\n    def url_params(self):\n        \"\"\"URL encoded strings with the GET params of the last request.\n\n        Intended for preserving GET params upon clicking a link by including\n        one (and only one) of these strings in the \"href\" attribute.\n\n        The parameters are divided into three groups: search, filters and\n        ordering. In addition to these three, the returned dict also contains\n        some combinations of them, as specified by the dict keys.\n\n        No leading \"?\" or \"&\".\n\n        :return dict with URL encoded strings.\n        \"\"\"\n\n        search = self._url_params_str('search')\n        filters = self._url_params_str('is_featured', 'maintainer')\n        ordering = self._url_params_str('order_by', 'ordering')\n\n        return {\n            'search': search,\n            'filters': filters,\n            'ordering': ordering,\n            'search_filters': self._join_url_params_strs(search, filters),\n            'filters_ordering': self._join_url_params_strs(filters, ordering),\n        }\n\n    def _url_params_str(self, *params):\n        args = map(lambda param: (param, self.request.GET.get(param, '')),\n                   params)\n        present_args = filter(lambda a: a[1], args)\n        return urlencode(dict(present_args))\n\n    def _join_url_params_strs(self, *strings):\n        return '&'.join(filter(None, strings))\n\n    @cached_property\n    def search_terms(self):\n        return self.request.GET.get('search', '').strip().split()\n\n\nclass AppUploadView(LoginRequiredMixin, TemplateView):\n    template_name = 'app/upload.html'\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context['form'] = AppReleaseUploadForm()\n        return context\n\n\nclass AppRegisterView(LoginRequiredMixin, TemplateView):\n    template_name = 'app/register.html'\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context['form'] = AppRegisterForm()\n        return context\n", "path": "nextcloudappstore/core/views.py"}], "after_files": [{"content": "from urllib.parse import urlencode\n\nfrom django.conf import settings\nfrom django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.contrib.auth.models import User\nfrom django.core.exceptions import ObjectDoesNotExist\nfrom django.db.models import Q\nfrom django.http import HttpResponse\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.utils.functional import cached_property\nfrom django.utils.translation import get_language, get_language_info\nfrom django.views.decorators.http import etag\nfrom django.views.generic.base import TemplateView\nfrom django.views.generic.detail import DetailView\nfrom django.views.generic.list import ListView\nfrom rest_framework.generics import ListAPIView\nfrom semantic_version import Version\n\nfrom nextcloudappstore.core.caching import app_etag\nfrom nextcloudappstore.core.facades import flatmap\nfrom nextcloudappstore.core.forms import AppRatingForm, AppReleaseUploadForm, \\\n    AppRegisterForm\nfrom nextcloudappstore.core.models import App, Category, AppRating\nfrom nextcloudappstore.core.serializers import AppRatingSerializer\nfrom nextcloudappstore.core.versioning import pad_min_version\n\n\n@etag(app_etag)\ndef app_description(request, id):\n    app = get_object_or_404(App, id=id)\n    return HttpResponse(app.description, content_type='text/plain')\n\n\nclass AppRatingApi(ListAPIView):\n    serializer_class = AppRatingSerializer\n\n    def get_queryset(self):\n        id = self.kwargs.get('id')\n        lang = self.request.GET.get('lang', self.request.LANGUAGE_CODE)\n        app = get_object_or_404(App, id=id)\n        queryset = AppRating.objects.language(lang).filter(app=app)\n\n        current_user = self.request.GET.get('current_user', 'false')\n        if current_user == 'true':\n            return queryset.filter(user=self.request.user)\n        else:\n            return queryset\n\n\nclass AppDetailView(DetailView):\n    queryset = App.objects.prefetch_related(\n        'releases',\n        'screenshots',\n        'co_maintainers',\n        'translations',\n    ).select_related('owner')\n    template_name = 'app/detail.html'\n    slug_field = 'id'\n    slug_url_kwarg = 'id'\n\n    def post(self, request, id):\n        form = AppRatingForm(request.POST, id=id, user=request.user)\n        # there is no way that a rating can be invalid by default\n        if form.is_valid() and request.user.is_authenticated:\n            form.save()\n        return redirect('app-detail', id=id)\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n\n        context['DISCOURSE_URL'] = settings.DISCOURSE_URL.rstrip('/')\n        context['rating_form'] = AppRatingForm(\n            initial={'language_code': get_language()})\n\n        ratings = AppRating.objects.filter(app=context['app'])\n        rating_languages = flatmap(\n            lambda r: r.get_available_languages(), ratings)\n\n        # make sure current session language is in the list even if there are\n        # no comments.\n        rating_languages = list(rating_languages)\n        if get_language() not in rating_languages:\n            rating_languages.append(get_language())\n\n        context['languages'] = set(sorted(rating_languages))\n        context['fallbackLang'] = 'en' if 'en' in context['languages'] else ''\n        context['user_has_rated_app'] = False\n        if self.request.user.is_authenticated:\n            try:\n                app_rating = AppRating.objects.get(user=self.request.user,\n                                                   app=context['app'])\n\n                # if parler falls back to a fallback language\n                # it doesn't set the language as current language\n                # and we can't select the correct language in the\n                # frontend. So we try and find a languge that is\n                # available\n                language_code = app_rating.get_current_language()\n                if not app_rating.has_translation(language_code):\n                    for fallback in app_rating.get_fallback_languages():\n                        if app_rating.has_translation(fallback):\n                            app_rating.set_current_language(fallback)\n\n                # when accessing an empty comment django-parler tries to\n                # fall back to the default language. However for comments\n                # the default (English) does not always exist. Unfortunately\n                # it throws the same exception as non existing models,\n                # so we need to access it beforehand\n                try:\n                    comment = app_rating.comment\n                except AppRating.DoesNotExist:\n                    comment = ''\n\n                context['rating_form'] = AppRatingForm({\n                    'rating': app_rating.rating,\n                    'comment': comment,\n                    'language_code': app_rating.get_current_language(),\n                })\n                context['user_has_rated_app'] = True\n            except AppRating.DoesNotExist:\n                pass\n        context['categories'] = Category.objects.prefetch_related(\n            'translations').all()\n        context['latest_releases_by_platform_v'] = \\\n            self.object.latest_releases_by_platform_v()\n        context['is_integration'] = self.object.is_integration\n        return context\n\n\nclass AppReleasesView(DetailView):\n    queryset = App.objects.prefetch_related(\n        'translations',\n        'releases__translations',\n        'releases__phpextensiondependencies__php_extension',\n        'releases__databasedependencies__database',\n        'releases__shell_commands',\n        'releases__licenses',\n    )\n    template_name = 'app/releases.html'\n    slug_field = 'id'\n    slug_url_kwarg = 'id'\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context['categories'] = Category.objects.prefetch_related(\n            'translations').all()\n\n        releases = self.object.releases_by_platform_v()\n        unstables = self.object.unstable_releases_by_platform_v()\n        versions = set(list(releases.keys()) + list(unstables.keys()))\n        all_releases = list(map(\n            lambda v: (v, releases.get(v, []) + unstables.get(v, [])),\n            versions))\n        context['releases_by_platform_v'] = \\\n            self._sort_by_platform_v(all_releases)\n        return context\n\n    def _sort_by_platform_v(self, releases_by_platform, reverse=True):\n        \"\"\"Sorts a list of tuples like (<platform version>, [releases]) by\n        platform version.\n\n        :param releases_by_platform: A list of tuples.\n        :param reverse: Descending order if True, ascending otherwise.\n        :return sorted list of tuples.\n        \"\"\"\n\n        return sorted(releases_by_platform, reverse=reverse,\n                      key=lambda v: Version(pad_min_version(v[0])))\n\n\nclass CategoryAppListView(ListView):\n    model = App\n    template_name = 'app/list.html'\n    allow_empty = True\n\n    def get_queryset(self):\n        order_by = self.request.GET.get('order_by', 'rating_overall')\n        ordering = self.request.GET.get('ordering', 'desc')\n        is_featured = self.request.GET.get('is_featured', False)\n        maintainer = self.request.GET.get('maintainer', False)\n        sort_columns = []\n\n        if self.kwargs.get('is_featured_category', False):\n            is_featured = \"true\"\n\n        allowed_order_by = {'name', 'last_release', 'rating_overall',\n                            'rating_recent'}\n        if order_by in allowed_order_by:\n            if order_by == 'name':\n                order_by = 'translations__name'\n            if ordering == 'desc':\n                sort_columns.append('-' + order_by)\n            else:\n                sort_columns.append(order_by)\n\n        lang = get_language_info(get_language())['code']\n        category_id = self.kwargs['id']\n        queryset = App.objects.search(self.search_terms, lang).order_by(\n            *sort_columns).filter(Q(releases__gt=0) | (Q(is_integration=True) &\n                                                       Q(approved=True)))\n        if maintainer:\n            try:\n                user = User.objects.get_by_natural_key(maintainer)\n                queryset = queryset.filter(Q(owner=user) |\n                                           Q(co_maintainers=user))\n            except ObjectDoesNotExist:\n                return queryset.none()\n        if category_id:\n            queryset = queryset.filter(categories__id=category_id)\n        if is_featured == \"true\":\n            queryset = queryset.filter(is_featured=True)\n        return queryset.prefetch_related('screenshots', 'translations')\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context['categories'] = Category.objects.prefetch_related(\n            'translations').all()\n        category_id = self.kwargs['id']\n        context['is_featured_category'] = self.kwargs \\\n            .get('is_featured_category', False)\n        if category_id:\n            context['current_category'] = get_object_or_404(Category,\n                                                            id=category_id)\n        if self.search_terms:\n            context['search_query'] = ' '.join(self.search_terms)\n        context['url_params'] = self.url_params\n        return context\n\n    @cached_property\n    def url_params(self):\n        \"\"\"URL encoded strings with the GET params of the last request.\n\n        Intended for preserving GET params upon clicking a link by including\n        one (and only one) of these strings in the \"href\" attribute.\n\n        The parameters are divided into three groups: search, filters and\n        ordering. In addition to these three, the returned dict also contains\n        some combinations of them, as specified by the dict keys.\n\n        No leading \"?\" or \"&\".\n\n        :return dict with URL encoded strings.\n        \"\"\"\n\n        search = self._url_params_str('search')\n        filters = self._url_params_str('is_featured', 'maintainer')\n        ordering = self._url_params_str('order_by', 'ordering')\n\n        return {\n            'search': search,\n            'filters': filters,\n            'ordering': ordering,\n            'search_filters': self._join_url_params_strs(search, filters),\n            'filters_ordering': self._join_url_params_strs(filters, ordering),\n        }\n\n    def _url_params_str(self, *params):\n        args = map(lambda param: (param, self.request.GET.get(param, '')),\n                   params)\n        present_args = filter(lambda a: a[1], args)\n        return urlencode(dict(present_args))\n\n    def _join_url_params_strs(self, *strings):\n        return '&'.join(filter(None, strings))\n\n    @cached_property\n    def search_terms(self):\n        return self.request.GET.get('search', '').strip().split()\n\n\nclass AppUploadView(LoginRequiredMixin, TemplateView):\n    template_name = 'app/upload.html'\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context['form'] = AppReleaseUploadForm()\n        return context\n\n\nclass AppRegisterView(LoginRequiredMixin, TemplateView):\n    template_name = 'app/register.html'\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context['form'] = AppRegisterForm()\n        return context\n", "path": "nextcloudappstore/core/views.py"}]}