• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nextcloud / appstore / 5001735545

pending completion
5001735545

Pull #1032

github

GitHub
Merge 19e60b66a into b218de160
Pull Request #1032: pre-commit configuration

11 of 18 new or added lines in 14 files covered. (61.11%)

1 existing line in 1 file now uncovered.

3998 of 4318 relevant lines covered (92.59%)

5.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.16
/nextcloudappstore/core/views.py
1
from urllib.parse import urlencode
6✔
2

3
from django.conf import settings
6✔
4
from django.contrib.auth.mixins import LoginRequiredMixin
6✔
5
from django.contrib.auth.models import User
6✔
6
from django.core.exceptions import ObjectDoesNotExist
6✔
7
from django.db.models import Q
6✔
8
from django.http import HttpResponse
6✔
9
from django.shortcuts import get_object_or_404, redirect
6✔
10
from django.utils.functional import cached_property
6✔
11
from django.utils.translation import get_language, get_language_info
6✔
12
from django.views.decorators.http import etag
6✔
13
from django.views.generic.base import TemplateView
6✔
14
from django.views.generic.detail import DetailView
6✔
15
from django.views.generic.list import ListView
6✔
16
from rest_framework.generics import ListAPIView
6✔
17
from semantic_version import Version
6✔
18

19
from nextcloudappstore.core.caching import app_etag
6✔
20
from nextcloudappstore.core.facades import flatmap
6✔
21
from nextcloudappstore.core.forms import (AppRatingForm, AppRegisterForm,
6✔
22
                                          AppReleaseUploadForm)
23
from nextcloudappstore.core.models import App, AppRating, Category
6✔
24
from nextcloudappstore.core.serializers import AppRatingSerializer
6✔
25
from nextcloudappstore.core.versioning import pad_min_version
6✔
26

27

28
@etag(app_etag)
6✔
29
def app_description(request, id):
6✔
30
    app = get_object_or_404(App, id=id)
6✔
31
    return HttpResponse(app.description, content_type='text/plain')
6✔
32

33

34
class AppRatingApi(ListAPIView):
6✔
35
    serializer_class = AppRatingSerializer
6✔
36

37
    def get_queryset(self):
6✔
38
        id = self.kwargs.get('id')
6✔
39
        lang = self.request.GET.get('lang', self.request.LANGUAGE_CODE)
6✔
40
        app = get_object_or_404(App, id=id)
6✔
41
        queryset = AppRating.objects.language(lang).filter(app=app)
6✔
42

43
        current_user = self.request.GET.get('current_user', 'false')
6✔
44
        if current_user == 'true':
6✔
45
            return queryset.filter(user=self.request.user)
×
46
        else:
47
            return queryset
6✔
48

49

50
class AppDetailView(DetailView):
6✔
51
    queryset = App.objects.prefetch_related(
6✔
52
        'releases',
53
        'screenshots',
54
        'co_maintainers',
55
        'translations',
56
    ).select_related('owner')
57
    template_name = 'app/detail.html'
6✔
58
    slug_field = 'id'
6✔
59
    slug_url_kwarg = 'id'
6✔
60

61
    def post(self, request, id):
6✔
62
        form = AppRatingForm(request.POST, id=id, user=request.user)
6✔
63
        # there is no way that a rating can be invalid by default
64
        if form.is_valid() and request.user.is_authenticated:
6✔
65
            form.save()
6✔
66
        return redirect('app-detail', id=id)
6✔
67

68
    def get_context_data(self, **kwargs):
6✔
69
        context = super().get_context_data(**kwargs)
6✔
70

71
        context['DISCOURSE_URL'] = settings.DISCOURSE_URL.rstrip('/')
6✔
72
        context['rating_form'] = AppRatingForm(
6✔
73
            initial={'language_code': get_language()})
74

75
        ratings = AppRating.objects.filter(app=context['app'])
6✔
76
        rating_languages = flatmap(
6✔
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)
6✔
82
        if get_language() not in rating_languages:
6✔
83
            rating_languages.append(get_language())
6✔
84

85
        context['languages'] = set(sorted(rating_languages))
6✔
86
        context['fallbackLang'] = 'en' if 'en' in context['languages'] else ''
6✔
87
        context['user_has_rated_app'] = False
6✔
88
        if self.request.user.is_authenticated:
6✔
89
            try:
6✔
90
                app_rating = AppRating.objects.get(user=self.request.user,
6✔
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()
6✔
99
                if not app_rating.has_translation(language_code):
6✔
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:
6✔
110
                    comment = app_rating.comment
6✔
111
                except AppRating.DoesNotExist:
×
112
                    comment = ''
×
113

114
                context['rating_form'] = AppRatingForm({
6✔
115
                    'rating': app_rating.rating,
116
                    'comment': comment,
117
                    'language_code': app_rating.get_current_language(),
118
                })
119
                context['user_has_rated_app'] = True
6✔
120
            except AppRating.DoesNotExist:
6✔
121
                pass
6✔
122
        context['categories'] = Category.objects.prefetch_related(
6✔
123
            'translations').all()
124
        context['latest_releases_by_platform_v'] = \
6✔
125
            self.object.latest_releases_by_platform_v()
126
        context['is_integration'] = self.object.is_integration
6✔
127
        return context
6✔
128

129

130
class AppReleasesView(DetailView):
6✔
131
    queryset = App.objects.prefetch_related(
6✔
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'
6✔
140
    slug_field = 'id'
6✔
141
    slug_url_kwarg = 'id'
6✔
142

143
    def get_context_data(self, **kwargs):
6✔
144
        context = super().get_context_data(**kwargs)
6✔
145
        context['categories'] = Category.objects.prefetch_related(
6✔
146
            'translations').all()
147

148
        releases = self.object.releases_by_platform_v()
6✔
149
        unstables = self.object.unstable_releases_by_platform_v()
6✔
150
        versions = set(list(releases.keys()) + list(unstables.keys()))
6✔
151
        all_releases = list(map(
6✔
152
            lambda v: (v, releases.get(v, []) + unstables.get(v, [])),
153
            versions))
154
        context['releases_by_platform_v'] = \
6✔
155
            self._sort_by_platform_v(all_releases)
156
        return context
6✔
157

158
    def _sort_by_platform_v(self, releases_by_platform, reverse=True):
6✔
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,
6✔
168
                      key=lambda v: Version(pad_min_version(v[0])))
169

170

171
class CategoryAppListView(ListView):
6✔
172
    model = App
6✔
173
    template_name = 'app/list.html'
6✔
174
    allow_empty = True
6✔
175

176
    def get_queryset(self):
6✔
177
        order_by = self.request.GET.get('order_by', 'rating_overall')
6✔
178
        ordering = self.request.GET.get('ordering', 'desc')
6✔
179
        is_featured = self.request.GET.get('is_featured', False)
6✔
180
        maintainer = self.request.GET.get('maintainer', False)
6✔
181
        sort_columns = []
6✔
182

183
        if self.kwargs.get('is_featured_category', False):
6✔
184
            is_featured = "true"
×
185

186
        allowed_order_by = {'name', 'last_release', 'rating_overall',
6✔
187
                            'rating_recent'}
188
        if order_by in allowed_order_by:
6✔
189
            if order_by == 'name':
6✔
190
                order_by = 'translations__name'
×
191
            if ordering == 'desc':
6✔
192
                sort_columns.append('-' + order_by)
6✔
193
            else:
194
                sort_columns.append(order_by)
×
195

196
        lang = get_language_info(get_language())['code']
6✔
197
        category_id = self.kwargs['id']
6✔
198
        queryset = App.objects.search(self.search_terms, lang).order_by(
6✔
199
            *sort_columns).filter(Q(releases__gt=0) | (Q(is_integration=True) & Q(approved=True)))
200
        if maintainer:
6✔
201
            try:
×
202
                user = User.objects.get_by_natural_key(maintainer)
×
NEW
203
                queryset = queryset.filter(Q(owner=user) | Q(co_maintainers=user))
×
UNCOV
204
            except ObjectDoesNotExist:
×
205
                return queryset.none()
×
206
        if category_id:
6✔
207
            queryset = queryset.filter(categories__id=category_id)
×
208
        if is_featured == "true":
6✔
209
            queryset = queryset.filter(is_featured=True)
×
210
        return queryset.prefetch_related('screenshots', 'translations')
6✔
211

212
    def get_context_data(self, **kwargs):
6✔
213
        context = super().get_context_data(**kwargs)
6✔
214
        context['categories'] = Category.objects.prefetch_related(
6✔
215
            'translations').all()
216
        category_id = self.kwargs['id']
6✔
217
        context['is_featured_category'] = self.kwargs \
6✔
218
            .get('is_featured_category', False)
219
        if category_id:
6✔
220
            context['current_category'] = get_object_or_404(Category,
×
221
                                                            id=category_id)
222
        if self.search_terms:
6✔
223
            context['search_query'] = ' '.join(self.search_terms)
×
224
        context['url_params'] = self.url_params
6✔
225
        return context
6✔
226

227
    @cached_property
6✔
228
    def url_params(self):
6✔
229
        """URL encoded strings with the GET params of the last request.
230

231
        Intended for preserving GET params upon clicking a link by including
232
        one (and only one) of these strings in the "href" attribute.
233

234
        The parameters are divided into three groups: search, filters and
235
        ordering. In addition to these three, the returned dict also contains
236
        some combinations of them, as specified by the dict keys.
237

238
        No leading "?" or "&".
239

240
        :return dict with URL encoded strings.
241
        """
242

243
        search = self._url_params_str('search')
6✔
244
        filters = self._url_params_str('is_featured', 'maintainer')
6✔
245
        ordering = self._url_params_str('order_by', 'ordering')
6✔
246

247
        return {
6✔
248
            'search': search,
249
            'filters': filters,
250
            'ordering': ordering,
251
            'search_filters': self._join_url_params_strs(search, filters),
252
            'filters_ordering': self._join_url_params_strs(filters, ordering),
253
        }
254

255
    def _url_params_str(self, *params):
6✔
256
        args = map(lambda param: (param, self.request.GET.get(param, '')),
6✔
257
                   params)
258
        present_args = filter(lambda a: a[1], args)
6✔
259
        return urlencode(dict(present_args))
6✔
260

261
    def _join_url_params_strs(self, *strings):
6✔
262
        return '&'.join(filter(None, strings))
6✔
263

264
    @cached_property
6✔
265
    def search_terms(self):
6✔
266
        return self.request.GET.get('search', '').strip().split()
6✔
267

268

269
class AppUploadView(LoginRequiredMixin, TemplateView):
6✔
270
    template_name = 'app/upload.html'
6✔
271

272
    def get_context_data(self, **kwargs):
6✔
273
        context = super().get_context_data(**kwargs)
6✔
274
        context['form'] = AppReleaseUploadForm()
6✔
275
        return context
6✔
276

277

278
class AppRegisterView(LoginRequiredMixin, TemplateView):
6✔
279
    template_name = 'app/register.html'
6✔
280

281
    def get_context_data(self, **kwargs):
6✔
282
        context = super().get_context_data(**kwargs)
6✔
283
        context['form'] = AppRegisterForm()
6✔
284
        return context
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc