def setUp(self): self.permission = GroupPermission('Drinkers', 'Beer') self.obj = Mock() self.profile = UserProfile.objects.get(pk=2519) self.anonymous = AnonymousUser() self.request = RequestFactory().get('/') self.request.user = self.anonymous
class VersionViewSet(CORSMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet): queryset = Version.objects.exclude(addon__status=mkt.STATUS_DELETED) serializer_class = VersionSerializer authentication_classes = [ RestOAuthAuthentication, RestSharedSecretAuthentication, RestAnonymousAuthentication ] permission_classes = [ AnyOf(AllowReadOnlyIfPublic, AllowRelatedAppOwner, GroupPermission('Apps', 'Review'), GroupPermission('Admin', '%')) ] cors_allowed_methods = ['get', 'patch', 'put'] def update(self, request, *args, **kwargs): """ Allow a version's features to be updated. """ obj = self.get_object() # Update features if they are provided. if 'features' in request.DATA: # Raise an exception if any invalid features are passed. invalid = [ f for f in request.DATA['features'] if f.upper() not in APP_FEATURES.keys() ] if any(invalid): raise ParseError('Invalid feature(s): %s' % ', '.join(invalid)) # Update the value of each feature (note: a feature not present in # the form data is assumed to be False) data = {} for key, name in APP_FEATURES.items(): field_name = 'has_' + key.lower() data[field_name] = key.lower() in request.DATA['features'] obj.features.update(**data) del request.DATA['features'] return super(VersionViewSet, self).update(request, *args, **kwargs) @action(methods=['PATCH'], cors_allowed_methods=VersionStatusViewSet.cors_allowed_methods) def status(self, request, *args, **kwargs): self.queryset = Version.with_deleted.all() kwargs['version'] = self.get_object() view = VersionStatusViewSet.as_view({'patch': 'update'}) return view(request, *args, **kwargs)
class AppEscalate(_AppAction, CreateAPIView, DestroyAPIView): permission_classes = [ByHttpMethod({ 'options': AllowAny, 'post': GroupPermission('Apps', 'Review'), 'delete': GroupPermission('Apps', 'Edit'), })] verb = "escalate" def delete(self, request, pk, *a, **kw): app = self.get_object() handler = ReviewApp(request, app, app.latest_version, ()) handler.set_data(request.QUERY_PARAMS) handler.process_clear_escalation() return Response()
class FeedAppViewSet(CORSMixin, MarketplaceView, SlugOrIdMixin, ImageURLUploadMixin): """ A viewset for the FeedApp class, which highlights a single app and some additional metadata (e.g. a review or a screenshot). """ authentication_classes = [ RestOAuthAuthentication, RestSharedSecretAuthentication, RestAnonymousAuthentication ] permission_classes = [ AnyOf(AllowReadOnly, GroupPermission('Feed', 'Curate')) ] filter_backends = (OrderingFilter, ) queryset = FeedApp.objects.all() cors_allowed_methods = ('get', 'delete', 'post', 'put', 'patch') serializer_class = FeedAppSerializer image_fields = (('background_image_upload_url', 'image_hash', ''), ) def list(self, request, *args, **kwargs): page = self.paginate_queryset(self.filter_queryset( self.get_queryset())) serializer = self.get_pagination_serializer(page) return response.Response(serializer.data)
class GlobalStats(CORSMixin, APIView): authentication_classes = (RestOAuthAuthentication, RestSharedSecretAuthentication) cors_allowed_methods = ['get'] permission_classes = [GroupPermission('Stats', 'View')] def get(self, request, metric): if metric not in STATS: raise http.Http404('No metric by that name.') stat = STATS[metric] # Perform form validation. form = StatsForm(request.GET) if not form.is_valid(): raise ParseError(dict(form.errors.items())) qs = form.cleaned_data dimensions = {} if 'dimensions' in stat: for key, default in stat['dimensions'].items(): val = request.GET.get(key, default) if val is not None: # Avoid passing kwargs to the monolith client when the # dimension is None to avoid facet filters being applied. dimensions[key] = request.GET.get(key, default) return Response( _get_monolith_data(stat, qs.get('start'), qs.get('end'), qs.get('interval'), dimensions))
class ReviewersSearchView(SearchView): cors_allowed_methods = ['get'] authentication_classes = [ RestSharedSecretAuthentication, RestOAuthAuthentication ] permission_classes = [GroupPermission('Apps', 'Review')] form_class = ApiReviewersSearchForm serializer_class = ReviewersESAppSerializer def search(self, request): # Parse form. form = self.form_class(request.GET if request else None) if not form.is_valid(): raise form_errors(form) form_data = form.cleaned_data # Status filter. data = search_form_to_es_fields(form_data) if form_data.get('status') != 'any': data.update(status=form_data.get('status')) # Do filter. sq = apply_reviewer_filters(request, WebappIndexer.search(), data=form_data) sq = WebappIndexer.get_app_filter(request, data, sq=sq, no_filter=True) page = self.paginate_queryset(sq) return self.get_pagination_serializer(page), request.GET.get('q', '')
class GlobalStatsTotal(CORSMixin, APIView, StatsTotalBase): authentication_classes = (RestOAuthAuthentication, RestSharedSecretAuthentication) cors_allowed_methods = ['get'] permission_classes = [GroupPermission('Stats', 'View')] slug_field = 'app_slug' def get(self, request): client = self.get_client() # Note: We have to do this as separate requests so that if one fails # the rest can still be returned. data = {} for metric, stat in STATS_TOTAL.items(): data[metric] = {} query = self.get_query(metric, stat['metric']) try: resp = client.raw(query) except ValueError as e: log.info('Received value error from monolith client: %s' % e) continue self.process_response(resp, data) return Response(data)
class AppStatsTotal(CORSMixin, SlugOrIdMixin, ListAPIView, StatsTotalBase): authentication_classes = (RestOAuthAuthentication, RestSharedSecretAuthentication) cors_allowed_methods = ['get'] permission_classes = [ AnyOf(AllowAppOwner, GroupPermission('Stats', 'View')) ] queryset = Webapp.objects.all() slug_field = 'app_slug' def get(self, request, pk): app = self.get_object() client = self.get_client() # Note: We have to do this as separate requests so that if one fails # the rest can still be returned. data = {} for metric, stat in APP_STATS_TOTAL.items(): data[metric] = {} query = self.get_query(metric, stat['metric'], app.id) try: resp = client.raw(query) except ValueError as e: log.info('Received value error from monolith client: %s' % e) continue self.process_response(resp, data) return Response(data)
class MonolithViewSet(CORSMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): cors_allowed_methods = ('get', 'delete') permission_classes = [GroupPermission('Monolith', 'API')] authentication_classes = [RestOAuthAuthentication] serializer_class = MonolithSerializer def get_queryset(self): form = MonolithForm(self.request.QUERY_PARAMS) if not form.is_valid(): return Response(form.errors, status=status.HTTP_400_BAD_REQUEST) key = form.cleaned_data['key'] start = form.cleaned_data['start'] end = form.cleaned_data['end'] qs = MonolithRecord.objects.all() if key: qs = qs.filter(key=key) if start is not None: qs = qs.filter(recorded__gte=start) if end is not None: qs = qs.filter(recorded__lt=end) return qs @transaction.commit_on_success def delete(self, request, *args, **kwargs): qs = self.filter_queryset(self.get_queryset()) logger.info('Deleting %d monolith resources' % qs.count()) qs.delete() return Response(status=status.HTTP_204_NO_CONTENT)
class CannedResponseViewSet(CORSMixin, MarketplaceView, viewsets.ModelViewSet): authentication_classes = (RestOAuthAuthentication, RestSharedSecretAuthentication) permission_classes = [GroupPermission('Admin', 'ReviewerTools')] model = CannedResponse serializer_class = CannedResponseSerializer cors_allowed_methods = ['get', 'post', 'patch', 'put', 'delete']
class FailureNotificationView(MarketplaceView, GenericAPIView): authentication_classes = [ RestOAuthAuthentication, RestSharedSecretAuthentication ] permission_classes = [GroupPermission('Transaction', 'NotifyFailure')] queryset = Contribution.objects.filter(uuid__isnull=False) def patch(self, request, *args, **kwargs): form = FailureForm(request.DATA) if not form.is_valid(): return Response(form.errors, status=status.HTTP_400_BAD_REQUEST) obj = self.get_object() data = { 'transaction_id': obj, 'transaction_url': absolutify( urlparams(reverse('mkt.developers.transactions'), transaction_id=obj.uuid)), 'url': form.cleaned_data['url'], 'retries': form.cleaned_data['attempts'] } owners = obj.addon.authors.values_list('email', flat=True) send_mail_jinja('Payment notification failure.', 'webpay/failure.txt', data, recipient_list=owners) return Response(status=status.HTTP_202_ACCEPTED)
class ReviewersWebsiteSearchView(WebsiteSearchView): permission_classes = [GroupPermission('Apps', 'Review')] filter_backends = [ SearchQueryFilter, ReviewerWebsiteSearchFormFilter, SortingFilter ] serializer_class = ReviewerESWebsiteSerializer form_class = ReviewersWebsiteSearchForm
class PriceTierViewSet(generics.CreateAPIView, generics.RetrieveUpdateDestroyAPIView, ModelViewSet): permission_classes = [GroupPermission('Prices', 'Edit')] authentication_classes = [RestOAuthAuthentication] serializer_class = PriceTierSerializer model = Price
class ProductIconViewSet(CORSMixin, MarketplaceView, ListModelMixin, RetrieveModelMixin, GenericViewSet): authentication_classes = [ RestOAuthAuthentication, RestSharedSecretAuthentication, RestAnonymousAuthentication ] permission_classes = [ AnyOf(AllowReadOnly, GroupPermission('ProductIcon', 'Create')) ] queryset = ProductIcon.objects.all() serializer_class = ProductIconSerializer cors_allowed_methods = ['get', 'post'] filter_fields = ('ext_url', 'ext_size', 'size') def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.DATA) if serializer.is_valid(): log.info('Resizing product icon %s @ %s to %s for webpay' % (serializer.data['ext_url'], serializer.data['ext_size'], serializer.data['size'])) tasks.fetch_product_icon.delay(serializer.data['ext_url'], serializer.data['ext_size'], serializer.data['size']) return Response(serializer.data, status=status.HTTP_202_ACCEPTED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class VersionStatusViewSet(mixins.UpdateModelMixin, viewsets.GenericViewSet): """Special API view used by senior reviewers and admins to modify a version (actually the corresponding File) status.""" authentication_classes = [ RestOAuthAuthentication, RestSharedSecretAuthentication, RestAnonymousAuthentication ] permission_classes = [GroupPermission('Admin', '%')] serializer_class = FileStatusSerializer cors_allowed_methods = ['patch'] def get_object(self): # Since we are fetching a totally different object than the pk the # client is passing, we need to make sure to override the pk in # self.kwargs, DRF uses it as a precautionary measure in in pre_save(). obj = self.kwargs['version'].all_files[0] self.kwargs[self.lookup_field] = obj.pk return obj def update(self, request, *args, **kwargs): # PUT is disallowed, only PATCH is accepted for this endpoint. if request.method == 'PUT': raise MethodNotAllowed('PUT') res = super(VersionStatusViewSet, self).update(request, *args, **kwargs) app = self.object.version.addon res.data['app_status'] = mkt.STATUS_CHOICES_API[app.status] return res
class MonolithView(CORSMixin, MarketplaceView, ListAPIView): cors_allowed_methods = ['get'] permission_classes = [GroupPermission('Monolith', 'API')] authentication_classes = [RestOAuthAuthentication] serializer_class = MonolithSerializer def get_queryset(self): form = MonolithForm(self.request.QUERY_PARAMS) if not form.is_valid(): return Response(form.errors, status=status.HTTP_400_BAD_REQUEST) key = form.cleaned_data['key'] start = form.cleaned_data['start'] end = form.cleaned_data['end'] log.info('[Monolith] Querying key:%s [%s:%s]' % (key, start, end)) if key in STATS: return _get_query_result(key, start, end) else: qs = MonolithRecord.objects.all() if key: qs = qs.filter(key=key) if start is not None: qs = qs.filter(recorded__gte=start) if end is not None: qs = qs.filter(recorded__lt=end) return qs
class TransactionAPI(CORSMixin, APIView): """ API to query by transaction ID. Note: This is intended for Monolith to be able to associate a Solitude transaction with an app and price tier amount in USD. """ authentication_classes = (RestOAuthAuthentication, RestSharedSecretAuthentication) cors_allowed_methods = ['get'] permission_classes = [GroupPermission('RevenueStats', 'View')] def get(self, request, transaction_id): try: contrib = (Contribution.objects.select_related('price_tier').get( transaction_id=transaction_id)) except Contribution.DoesNotExist: raise http.Http404('No transaction by that ID.') data = { 'id': transaction_id, 'app_id': contrib.addon_id, 'amount_USD': contrib.price_tier.price, 'type': amo.CONTRIB_TYPES[contrib.type], } return Response(data)
class ReviewingView(ListAPIView): authentication_classes = [RestOAuthAuthentication, RestSharedSecretAuthentication] permission_classes = [GroupPermission('Apps', 'Review')] serializer_class = ReviewingSerializer def get_queryset(self): return [row['app'] for row in AppsReviewing(self.request).get_apps()]
class FeedItemViewSet(viewsets.ModelViewSet): authentication_classes = [RestOAuthAuthentication, RestSharedSecretAuthentication, RestAnonymousAuthentication] permission_classes = [AnyOf(AllowReadOnly, GroupPermission('Feed', 'Curate'))] queryset = FeedItem.objects.all() serializer_class = FeedItemSerializer
class FeedBuilderView(CORSMixin, APIView): authentication_classes = [ RestOAuthAuthentication, RestSharedSecretAuthentication ] permission_classes = [GroupPermission('Feed', 'Curate')] cors_allowed_methods = ('put', ) def put(self, request, *args, **kwargs): """ For each region in the object: Deletes all of the (carrier-less) FeedItems in the region. Batch create all of the FeedItems in order for each region. -- feed - object of regions that point to a list of feed element IDs (as well as their type) . { 'us': [ ['app', 36L], ['app', 42L], ['collection', 12L], ['brand', 12L] ] } """ regions = [ mkt.regions.REGIONS_DICT[region].id for region in request.DATA.keys() ] FeedItem.objects.filter(carrier=None, region__in=regions).delete() feed_items = [] for region, feed_elements in request.DATA.items(): for order, feed_element in enumerate(feed_elements): try: item_type, item_id = feed_element except ValueError: return response.Response( 'Expected two-element arrays.', status=status.HTTP_400_BAD_REQUEST) feed_item = { 'region': mkt.regions.REGIONS_DICT[region].id, 'order': order, 'item_type': item_type, } feed_item[item_type + '_id'] = item_id feed_items.append(FeedItem(**feed_item)) FeedItem.objects.bulk_create(feed_items) # Index the feed items created. bulk_create doesn't call save or # post_save so get the IDs manually. feed_item_ids = list( FeedItem.objects.filter(region__in=regions).values_list('id', flat=True)) FeedItem.get_indexer().index_ids(feed_item_ids, no_delay=True) return response.Response(status=status.HTTP_201_CREATED)
class CollectionImageViewSet(CORSMixin, SlugOrIdMixin, MarketplaceView, generics.GenericAPIView, viewsets.ViewSet): permission_classes = [ AnyOf(AllowReadOnly, GroupPermission('Feed', 'Curate')) ] authentication_classes = [ RestOAuthAuthentication, RestSharedSecretAuthentication, RestAnonymousAuthentication ] cors_allowed_methods = ('get', 'put', 'delete') hash_field = 'image_hash' image_suffix = '' # Dummy serializer to keep DRF happy when it's answering to OPTIONS. serializer_class = Serializer def perform_content_negotiation(self, request, force=False): """ Force DRF's content negociation to not raise an error - It wants to use the format passed to the URL, but we don't care since we only deal with "raw" content: we don't even use the renderers. """ return super(CollectionImageViewSet, self).perform_content_negotiation(request, force=True) @cache_control(max_age=60 * 60 * 24 * 365) def retrieve(self, request, *args, **kwargs): obj = self.get_object() if not getattr(obj, 'image_hash', None): raise Http404 return HttpResponseSendFile(request, obj.image_path(self.image_suffix), content_type='image/png') def update(self, request, *args, **kwargs): obj = self.get_object() try: img, hash_ = DataURLImageField().from_native(request.read()) except ValidationError: return Response(status=status.HTTP_400_BAD_REQUEST) i = Image.open(img) with public_storage.open(obj.image_path(self.image_suffix), 'wb') as f: i.save(f, 'png') # Store the hash of the original image data sent. obj.update(**{self.hash_field: hash_}) pngcrush_image.delay(obj.image_path(self.image_suffix)) return Response(status=status.HTTP_204_NO_CONTENT) def destroy(self, request, *args, **kwargs): obj = self.get_object() if getattr(obj, 'image_hash', None): public_storage.delete(obj.image_path(self.image_suffix)) obj.update(**{self.hash_field: None}) return Response(status=status.HTTP_204_NO_CONTENT)
class FeedElementSearchView(BaseFeedESView): """ Search view for the Curation Tools. Returns an object keyed by feed element type ('apps', 'brands', 'collections'). """ authentication_classes = [ RestOAuthAuthentication, RestSharedSecretAuthentication ] permission_classes = [GroupPermission('Feed', 'Curate')] cors_allowed_methods = ('get', ) def _phrase(self, q): return { 'query': q, 'type': 'phrase', 'slop': 2, } def get(self, request, *args, **kwargs): q = request.GET.get('q') # Make search. queries = [ query.Q('match', slug=self._phrase(q)), # Slug. query.Q('match', type=self._phrase(q)), # Type. query.Q('match', search_names=self._phrase(q)), # Name. query.Q('prefix', carrier=q), # Shelf carrier. query.Q('term', region=q) # Shelf region. ] sq = query.Bool(should=queries) # Search. res = {'apps': [], 'brands': [], 'collections': [], 'shelves': []} es = Search(using=FeedItemIndexer.get_es(), index=self.get_feed_element_index()) feed_elements = es.query(sq).execute().hits if not feed_elements: return response.Response(res, status=status.HTTP_404_NOT_FOUND) # Deserialize. ctx = { 'app_map': self.get_apps(request, self.get_app_ids_all(feed_elements)), 'request': request } for feed_element in feed_elements: item_type = feed_element.item_type serializer = self.SERIALIZERS[item_type] data = serializer(feed_element, context=ctx).data res[self.PLURAL_TYPES[item_type]].append(data) # Return. return response.Response(res, status=status.HTTP_200_OK)
class FeedShelfPublishView(CORSMixin, APIView): """ put -- creates a FeedItem for a FeedShelf with respective carrier/region pair. Deletes any currently existing FeedItems with the carrier/region pair to effectively "unpublish" it since only one shelf can be toggled at a time for a carrier/region. delete -- deletes the FeedItem for a FeedShelf with respective carrier/region. """ authentication_classes = [RestOAuthAuthentication, RestSharedSecretAuthentication] permission_classes = [GroupPermission('Feed', 'Curate')] cors_allowed_methods = ('delete', 'put',) def get_object(self, pk): if pk.isdigit(): return FeedShelf.objects.get(pk=pk) else: return FeedShelf.objects.get(slug=pk) def put(self, request, *args, **kwargs): try: shelf = self.get_object(self.kwargs['pk']) except FeedShelf.DoesNotExist: return response.Response(status=status.HTTP_404_NOT_FOUND) feed_item_kwargs = { 'item_type': feed.FEED_TYPE_SHELF, 'carrier': shelf.carrier, 'region': shelf.region } FeedItem.objects.filter(**feed_item_kwargs).delete() feed_item = FeedItem.objects.create(shelf_id=shelf.id, **feed_item_kwargs) # Return. return response.Response(FeedItemSerializer(feed_item).data, status=status.HTTP_201_CREATED) def delete(self, request, *args, **kwargs): try: shelf = self.get_object(self.kwargs['pk']) except FeedShelf.DoesNotExist: return response.Response(status=status.HTTP_404_NOT_FOUND) feed_item_kwargs = { 'item_type': feed.FEED_TYPE_SHELF, 'carrier': shelf.carrier, 'region': shelf.region } FeedItem.objects.filter(**feed_item_kwargs).delete() # Return. return response.Response(status=status.HTTP_204_NO_CONTENT)
class FeedAppViewSet(CORSMixin, viewsets.ModelViewSet): authentication_classes = [ RestOAuthAuthentication, RestSharedSecretAuthentication, RestAnonymousAuthentication ] permission_classes = [ AnyOf(AllowReadOnly, GroupPermission('Feed', 'Curate')) ] queryset = FeedApp.objects.all() cors_allowed_methods = ('get', 'post') serializer_class = FeedAppSerializer
class WebsiteSubmissionViewSet(CORSMixin, MarketplaceView, viewsets.ModelViewSet): cors_allowed_methods = ['get', 'post'] authentication_classes = [ RestSharedSecretAuthentication, RestOAuthAuthentication ] queryset = WebsiteSubmission.objects.all() permission_classes = [GroupPermission('Websites', 'Submit')] serializer_class = PublicWebsiteSubmissionSerializer def pre_save(self, obj): setattr(obj, 'submitter', self.request.user)
class ReviewerScoreViewSet(CORSMixin, MarketplaceView, viewsets.ModelViewSet): authentication_classes = (RestOAuthAuthentication, RestSharedSecretAuthentication) permission_classes = [GroupPermission('Admin', 'ReviewerTools')] serializer_class = ReviewerScoreSerializer cors_allowed_methods = ['get', 'post', 'patch', 'put', 'delete'] # amo.REVIEWED_MANUAL is the default so we don't need to set it on the # instance when we are creating a new one, but we do need to set it on # queryset to prevent instances with other note_key values from ever being # returned. queryset = ReviewerScore.objects.filter(note_key=amo.REVIEWED_MANUAL)
class WebsiteLookupSearchView(WebsiteSearchView): permission_classes = [GroupPermission('WebsiteLookup', 'View')] filter_backends = [SearchQueryFilter] serializer_class = WebsiteLookupSerializer paginate_by = lkp.SEARCH_LIMIT max_paginate_by = lkp.MAX_RESULTS def get_paginate_by(self, *args, **kwargs): if self.request.GET.get(self.paginate_by_param) == 'max': return self.max_paginate_by else: return super(WebsiteLookupSearchView, self).get_paginate_by(*args, **kwargs)
class NoRegionSearchView(SearchView): """ A search view that allows searching for public apps regardless of region exclusions, protected behind a permission class. A special class is needed because when RegionFilter is included, as it is in the default SearchView, it will always use whatever region was set on the request, and we default to setting restofworld when no region is passed. """ authentication_classes = [ RestSharedSecretAuthentication, RestOAuthAuthentication ] permission_classes = [ AnyOf(GroupPermission('Feed', 'Curate'), GroupPermission('OperatorDashboard', '*'), IsOperatorPermission) ] filter_backends = [ SearchQueryFilter, PublicSearchFormFilter, PublicAppsFilter, DeviceTypeFilter, ProfileFilter, SortingFilter ]
class FeedElementListView(BaseFeedESView, MarketplaceView, generics.GenericAPIView): """ Fetches the five most recent of a feed element type for Curation Tools. With pagination. """ authentication_classes = [ RestOAuthAuthentication, RestSharedSecretAuthentication ] permission_classes = [GroupPermission('Feed', 'Curate')] cors_allowed_methods = ('get', ) paginator_class = ESPaginator def get_recent_feed_elements(self, sq): """Matches all sorted by recent.""" return sq.sort('-created').query(query.MatchAll()) def get(self, request, item_type, **kwargs): item_type = self.ITEM_TYPES[item_type] # Hit ES. sq = self.get_recent_feed_elements( Search(using=FeedItemIndexer.get_es(), index=self.INDICES[item_type])) feed_elements = self.paginate_queryset(sq) if not feed_elements: return response.Response({'objects': []}, status=status.HTTP_404_NOT_FOUND) # Deserialize. Manually use pagination serializer because this view # uses multiple serializers. meta = mkt.api.paginator.CustomPaginationSerializer(feed_elements, context={ 'request': request }).data['meta'] objects = self.SERIALIZERS[item_type]( feed_elements, context={ 'app_map': self.get_apps(request, self.get_app_ids_all(feed_elements)), 'request': request }, many=True).data return response.Response({ 'meta': meta, 'objects': objects }, status=status.HTTP_200_OK)
class StatusViewSet(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, GenericViewSet): queryset = Webapp.objects.all() authentication_classes = [ RestOAuthAuthentication, RestSharedSecretAuthentication ] permission_classes = [AnyOf(AllowAppOwner, GroupPermission('Admin', '%s'))] serializer_class = AppStatusSerializer def update(self, request, *args, **kwargs): # PUT is disallowed, only PATCH is accepted for this endpoint. if request.method == 'PUT': raise MethodNotAllowed('PUT') return super(StatusViewSet, self).update(request, *args, **kwargs)
class TestGroupPermission(TestCase): fixtures = fixture('user_2519') def setUp(self): self.permission = GroupPermission('Drinkers', 'Beer') self.obj = Mock() self.profile = UserProfile.objects.get(pk=2519) self.anonymous = AnonymousUser() self.request = RequestFactory().get('/') self.request.user = self.anonymous def test_has_permission_user_without(self): self.request.user = self.profile self.request.amo_user = self.profile self.request.groups = self.profile.groups.all() self.grant_permission(self.profile, 'Drinkers:Scotch') eq_(self.permission.has_permission(self.request, 'myview'), False) def test_has_permission_user_with(self): self.request.user = self.profile self.request.amo_user = self.profile self.request.groups = self.profile.groups.all() self.grant_permission(self.profile, 'Drinkers:Beer') eq_(self.permission.has_permission(self.request, 'myview'), True) def test_has_permission_anonymous(self): eq_(self.permission.has_permission(self.request, 'myview'), False) def test_has_object_permission_user_without(self): self.request.user = self.profile self.request.amo_user = self.profile self.request.groups = self.profile.groups.all() self.grant_permission(self.profile, 'Drinkers:Scotch') obj = Mock() eq_(self.permission.has_object_permission(self.request, 'myview', obj), False) def test_has_object_permission_user_with(self): self.request.user = self.profile self.request.amo_user = self.profile self.request.groups = self.profile.groups.all() self.grant_permission(self.profile, 'Drinkers:Beer') obj = Mock() eq_(self.permission.has_object_permission(self.request, 'myview', obj), True) def test_has_object_permission_anonymous(self): obj = Mock() eq_(self.permission.has_object_permission(self.request, 'myview', obj), False)
class TestGroupPermission(TestCase): fixtures = fixture("user_2519") def setUp(self): self.permission = GroupPermission("Drinkers", "Beer") self.obj = Mock() self.profile = UserProfile.objects.get(pk=2519) self.anonymous = AnonymousUser() self.request = RequestFactory().get("/") self.request.user = self.anonymous def test_has_permission_user_without(self): self.request.user = self.profile.user self.request.amo_user = self.profile self.request.groups = self.profile.groups.all() self.grant_permission(self.profile, "Drinkers:Scotch") eq_(self.permission.has_permission(self.request, "myview"), False) def test_has_permission_user_with(self): self.request.user = self.profile.user self.request.amo_user = self.profile self.request.groups = self.profile.groups.all() self.grant_permission(self.profile, "Drinkers:Beer") eq_(self.permission.has_permission(self.request, "myview"), True) def test_has_permission_anonymous(self): eq_(self.permission.has_permission(self.request, "myview"), False) def test_has_object_permission_user_without(self): self.request.user = self.profile.user self.request.amo_user = self.profile self.request.groups = self.profile.groups.all() self.grant_permission(self.profile, "Drinkers:Scotch") obj = Mock() eq_(self.permission.has_object_permission(self.request, "myview", obj), False) def test_has_object_permission_user_with(self): self.request.user = self.profile.user self.request.amo_user = self.profile self.request.groups = self.profile.groups.all() self.grant_permission(self.profile, "Drinkers:Beer") obj = Mock() eq_(self.permission.has_object_permission(self.request, "myview", obj), True) def test_has_object_permission_anonymous(self): obj = Mock() eq_(self.permission.has_object_permission(self.request, "myview", obj), False)