コード例 #1
0
 def test_has_object_permission(self):
     request = RequestFactory().get('/')
     ok_(
         AnyOf(AllowNone,
               AllowAny)().has_object_permission(request, 'myview', None))
     ok_(
         AnyOf(AllowAny,
               AllowNone)().has_object_permission(request, 'myview', None))
コード例 #2
0
ファイル: views.py プロジェクト: waseem18/zamboni
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():
            serializer.save()
            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)
コード例 #3
0
ファイル: views.py プロジェクト: waseem18/zamboni
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', 'image_hash', ''), )

    def list(self, request, *args, **kwargs):
        page = self.paginate_queryset(self.filter_queryset(
            self.get_queryset()))
        serializer = self.get_serializer(page, many=True)
        return self.get_paginated_response(serializer.data)
コード例 #4
0
ファイル: views.py プロジェクト: waseem18/zamboni
class AppStatsTotal(CORSMixin, SlugOrIdMixin, ListAPIView, StatsTotalBase):
    authentication_classes = (RestOAuthAuthentication,
                              RestSharedSecretAuthentication)
    cors_allowed_methods = ['get']
    permission_classes = [AnyOf(PublicStats, 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)
コード例 #5
0
ファイル: views.py プロジェクト: waseem18/zamboni
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 get_file_response(request,
                                 obj.image_path(self.image_suffix),
                                 content_type='image/png',
                                 public=True)

    def update(self, request, *args, **kwargs):
        obj = self.get_object()
        try:
            img, hash_ = image_from_data_url(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)
コード例 #6
0
ファイル: views.py プロジェクト: Witia1/zamboni
 def get_object(self, queryset=None):
     try:
         app = super(AppViewSet, self).get_object()
     except Http404:
         app = super(AppViewSet, self).get_object(self.get_base_queryset())
         # Owners and reviewers can see apps regardless of region.
         owner_or_reviewer = AnyOf(AllowAppOwner, AllowReviewerReadOnly)
         if owner_or_reviewer.has_object_permission(self.request, self,
                                                    app):
             return app
         data = {}
         for key in ('name', 'support_email', 'support_url'):
             value = getattr(app, key)
             data[key] = unicode(value) if value else ''
         data['reason'] = 'Not available in your region.'
         raise HttpLegallyUnavailable(data)
     self.check_object_permissions(self.request, app)
     return app
コード例 #7
0
ファイル: views.py プロジェクト: amuntner/zamboni
 def get_object(self, queryset=None):
     try:
         app = super(AppViewSet, self).get_object()
     except Http404:
         app = super(AppViewSet, self).get_object(self.get_base_queryset())
         # Owners and reviewers can see apps regardless of region.
         owner_or_reviewer = AnyOf(AllowAppOwner, AllowReviewerReadOnly)
         if owner_or_reviewer.has_object_permission(self.request, self,
                                                    app):
             return app
         data = {}
         for key in ('name', 'support_email', 'support_url'):
             value = getattr(app, key)
             data[key] = unicode(value) if value else ''
         data['reason'] = 'Not available in your region.'
         raise HttpLegallyUnavailable(data)
     self.check_object_permissions(self.request, app)
     return app
コード例 #8
0
class LangPackViewSet(CORSMixin, MarketplaceView, viewsets.ModelViewSet):
    model = LangPack
    queryset = LangPack.objects.all()
    cors_allowed_methods = ('get', 'post', 'put', 'patch', 'delete')
    permission_classes = [
        AnyOf(AllowReadOnlyIfPublic, GroupPermission('LangPacks', '%'))
    ]
    authentication_classes = [
        RestOAuthAuthentication, RestSharedSecretAuthentication,
        RestAnonymousAuthentication
    ]
    serializer_class = LangPackSerializer

    def filter_queryset(self, qs):
        """Filter GET requests with active=True, unless we have the permissions
        to do differently."""
        if self.request.method == 'GET':
            active_parameter = self.request.GET.get('active')
            fxos_version_parameter = self.request.GET.get('fxos_version')

            if 'pk' in self.kwargs:
                # No filtering at all if we're trying to see a detail page: the
                # permission_classes mechanism will handle the rest for us. It
                # reveals the existence of the langpack but that's OK.
                return qs

            # Handle 'active' filtering.
            if active_parameter in ('null', 'false'):
                if action_allowed(self.request, 'LangPacks', '%'):
                    # If active=null, we don't need to filter at all (we show
                    # all langpacks regardless of their 'active' flag value).
                    # If it's false, we only show inactive langpacks.
                    if active_parameter == 'false':
                        qs = qs.filter(active=False)
                else:
                    # We don't have the permission, but the parameter to filter
                    # was passed, return a permission denied, someone is trying
                    # to see things he shouldn't be able to see.
                    self.permission_denied(self.request)
            else:
                qs = qs.filter(active=True)

            # Handle 'fxos_version' filtering if necessary.
            if fxos_version_parameter:
                qs = qs.filter(fxos_version=fxos_version_parameter)
        return qs

    def get_serializer_class(self):
        if self.request.method in ('POST', 'PUT'):
            # When using POST or PUT, we are uploading a new package.
            return LangPackUploadSerializer
        else:
            return LangPackSerializer
コード例 #9
0
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)

    @detail_route(
        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)
コード例 #10
0
ファイル: views.py プロジェクト: wangeek/zamboni
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)
コード例 #11
0
ファイル: views.py プロジェクト: waseem18/zamboni
class ExtensionViewSet(CORSMixin, MarketplaceView, CreateExtensionMixin,
                       DestroyModelMixin, ListModelMixin, RetrieveModelMixin,
                       SlugOrIdMixin, UpdateModelMixin, GenericViewSet):
    authentication_classes = [
        RestOAuthAuthentication, RestSharedSecretAuthentication,
        RestAnonymousAuthentication
    ]
    cors_allowed_methods = ('get', 'patch', 'put', 'post', 'delete')
    model = Extension
    permission_classes = [
        AnyOf(AllowReadOnlyIfPublic, AllowOwnerButReadOnlyIfBlocked,
              AllowExtensionReviewerReadOnly, GroupPermission('Admin', '%'))
    ]
    queryset = Extension.objects.without_deleted()
    serializer_class = ExtensionSerializer

    def filter_queryset(self, qs):
        if self.action == 'list':
            # The listing API only allows you to see extensions you've
            # developed.
            if not self.request.user.is_authenticated():
                raise exceptions.PermissionDenied(
                    'Anonymous listing not allowed.')
            qs = qs.filter(authors=self.request.user)
        return qs

    def update(self, request, *args, **kwargs):
        partial = kwargs.get('partial', False)
        if not partial:
            # PUT are not supported, only PATCH is.
            raise exceptions.MethodNotAllowed(request.method)
        return super(ExtensionViewSet, self).update(request, *args, **kwargs)

    @detail_route(methods=['post'],
                  cors_allowed_methods=['post'],
                  permission_classes=[GroupPermission('Admin', '%')])
    def block(self, request, *args, **kwargs):
        obj = self.get_object()
        obj.block()
        return Response(status=status.HTTP_202_ACCEPTED)

    @detail_route(methods=['post'],
                  cors_allowed_methods=['post'],
                  permission_classes=[GroupPermission('Admin', '%')])
    def unblock(self, request, *args, **kwargs):
        obj = self.get_object()
        obj.unblock()
        return Response(status=status.HTTP_202_ACCEPTED)
コード例 #12
0
ファイル: views.py プロジェクト: waseem18/zamboni
class FeedItemViewSet(CORSMixin, viewsets.ModelViewSet):
    """
    A viewset for the FeedItem class, which wraps all items that live on the
    feed.
    """
    authentication_classes = [
        RestOAuthAuthentication, RestSharedSecretAuthentication,
        RestAnonymousAuthentication
    ]
    permission_classes = [
        AnyOf(AllowReadOnly, GroupPermission('Feed', 'Curate'))
    ]
    filter_backends = (OrderingFilter, RegionCarrierFilter)
    queryset = FeedItem.objects.all()
    cors_allowed_methods = ('get', 'delete', 'post', 'put', 'patch')
    serializer_class = FeedItemSerializer
コード例 #13
0
ファイル: views.py プロジェクト: varunnaganathan/zamboni
class PrivacyPolicyViewSet(CORSMixin, SlugOrIdMixin, MarketplaceView,
                           viewsets.GenericViewSet):
    queryset = Webapp.objects.all()
    cors_allowed_methods = ('get',)
    permission_classes = [AnyOf(AllowAppOwner, AllowReviewerReadOnly,
                                AllowReadOnlyIfPublic)]
    slug_field = 'app_slug'
    authentication_classes = [RestOAuthAuthentication,
                              RestSharedSecretAuthentication,
                              RestAnonymousAuthentication]

    def retrieve(self, request, *args, **kwargs):
        app = self.get_object()
        return response.Response(
            {'privacy_policy': unicode(app.privacy_policy)},
            content_type='application/json')
コード例 #14
0
ファイル: views.py プロジェクト: amuntner/zamboni
class NoteListView(CORSMixin, ListAPIView, MarketplaceView):
    serializer_class = NoteForListSerializer
    authentication_classes = [
        RestOAuthAuthentication, RestSharedSecretAuthentication
    ]
    permission_classes = [
        AnyOf(GroupPermission('Apps', 'Review'),
              GroupPermission('ContentTools', 'AddonReview'))
    ]
    filter_backends = (
        NoteContentFilter,
        NoteContentTypeFilter,
        OrderingFilter,
    )
    cors_allowed_methods = ['get']

    def get_queryset(self):
        return (CommunicationNote.objects.all().select_related(
            'thread', 'author').prefetch_related('attachments'))
コード例 #15
0
ファイル: views.py プロジェクト: waseem18/zamboni
class AppStats(CORSMixin, SlugOrIdMixin, ListAPIView):
    authentication_classes = (RestOAuthAuthentication,
                              RestSharedSecretAuthentication)
    cors_allowed_methods = ['get']
    permission_classes = [AnyOf(PublicStats, AllowAppOwner,
                                GroupPermission('Stats', 'View'))]
    queryset = Webapp.objects.all()
    slug_field = 'app_slug'

    def get(self, request, pk, metric):
        if metric not in APP_STATS:
            raise http.Http404('No metric by that name.')

        app = self.get_object()

        stat = APP_STATS[metric]

        # Perform form validation.
        form = StatsForm(request.GET)
        if not form.is_valid():
            exc = ParseError()
            exc.detail = {'detail': dict(form.errors.items())}
            raise exc

        qs = form.cleaned_data

        dimensions = {'app-id': app.id}

        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))
コード例 #16
0
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, PublicContentFilter,
        DeviceTypeFilter, ProfileFilter, SortingFilter
    ]
コード例 #17
0
ファイル: views.py プロジェクト: amuntner/zamboni
class RatingViewSet(CORSMixin, MarketplaceView, ModelViewSet):
    # Unfortunately, the model class name for ratings is "Review".
    # We prefetch 'version' because it's often going to be similar, and select
    # related 'user' to avoid extra queries.
    queryset = (Review.objects.valid().prefetch_related(
        'version').select_related('user'))
    cors_allowed_methods = ('get', 'post', 'put', 'delete')
    permission_classes = [
        ByHttpMethod({
            'options':
            AllowAny,  # Needed for CORS.
            'get':
            AllowAny,
            'head':
            AllowAny,
            'post':
            IsAuthenticated,
            'put':
            AnyOf(AllowOwner, GroupPermission('Apps', 'Edit')),
            'delete':
            AnyOf(AllowOwner, AllowRelatedAppOwner,
                  GroupPermission('Users', 'Edit'),
                  GroupPermission('Apps', 'Edit')),
        })
    ]
    authentication_classes = [
        RestOAuthAuthentication, RestSharedSecretAuthentication,
        RestAnonymousAuthentication
    ]
    serializer_class = RatingSerializer

    def paginator_class(self, *args, **kwargs):
        paginator = super(RatingViewSet, self).paginator_class(*args, **kwargs)
        if hasattr(self, 'app'):
            # If an app is passed, we want the paginator count to match the
            # number of reviews on the app, without doing an extra query.
            paginator._count = self.app.total_reviews
        return paginator

    # FIXME: Add throttling ? Original tastypie version didn't have it...

    def filter_queryset(self, queryset):
        """
        Custom filter method allowing us to filter on app slug/pk and user pk
        (or the special user value "mine"). A full FilterSet is overkill here.
        """
        filters = Q()
        app = self.request.GET.get('app')
        user = self.request.GET.get('user')
        lang = self.request.GET.get('lang')
        match_lang = self.request.GET.get('match_lang')
        if app:
            self.app = self.get_app(app)
            filters &= Q(addon=self.app)
        if user:
            filters &= Q(user=self.get_user(user))
        elif lang and match_lang == '1':
            filters &= Q(lang=lang)

        if filters:
            queryset = queryset.filter(filters)
        return queryset

    def get_user(self, ident):
        pk = ident
        if pk == 'mine':
            user = mkt.get_user()
            if not user or not user.is_authenticated():
                # You must be logged in to use "mine".
                raise NotAuthenticated()
            pk = user.pk
        return pk

    def get_app(self, ident):
        try:
            app = Webapp.objects.by_identifier(ident)
        except Webapp.DoesNotExist:
            raise Http404

        if not app.is_public() and not check_addon_ownership(
                self.request, app):
            # App owners and admin can see the app even if it's not public.
            # Regular users or anonymous users can't.
            raise PermissionDenied('The app requested is not public')
        return app

    def list(self, request, *args, **kwargs):
        response = super(RatingViewSet, self).list(request, *args, **kwargs)
        app = getattr(self, 'app', None)
        if app:
            user, info = self.get_extra_data(app, request.user)
            response.data['user'] = user
            response.data['info'] = info
        return response

    def destroy(self, request, *args, **kwargs):
        obj = self.get_object()
        mkt.log(mkt.LOG.DELETE_REVIEW,
                obj.addon,
                obj,
                details=dict(title=unicode(obj.title),
                             body=unicode(obj.body),
                             addon_id=obj.addon.id,
                             addon_title=unicode(obj.addon.name)))
        log.debug('[Review:%s] Deleted by %s' % (obj.pk, self.request.user.id))
        return super(RatingViewSet, self).destroy(request, *args, **kwargs)

    def post_save(self, obj, created=False):
        app = obj.addon
        if created:
            mkt.log(mkt.LOG.ADD_REVIEW, app, obj)
            log.debug('[Review:%s] Created by user %s ' %
                      (obj.pk, self.request.user.id))
            record_action('new-review', self.request, {'app-id': app.id})
        else:
            mkt.log(mkt.LOG.EDIT_REVIEW, app, obj)
            log.debug('[Review:%s] Edited by %s' %
                      (obj.pk, self.request.user.id))

    def partial_update(self, *args, **kwargs):
        # We don't need/want PATCH for now.
        raise MethodNotAllowed('PATCH is not supported for this endpoint.')

    def get_extra_data(self, app, user):
        extra_user = None

        if user.is_authenticated():
            if app.is_premium():
                # If the app is premium, you need to purchase it to rate it.
                can_rate = app.has_purchased(user)
            else:
                # If the app is free, you can not be one of the authors.
                can_rate = not app.has_author(user)

            filters = {'addon': app, 'user': user}
            if app.is_packaged:
                filters['version'] = app.current_version

            extra_user = {
                'can_rate': can_rate,
                'has_rated': Review.objects.valid().filter(**filters).exists()
            }

        extra_info = {
            'average': app.average_rating,
            'slug': app.app_slug,
            'total_reviews': app.total_reviews,
            'current_version': getattr(app.current_version, 'version', None)
        }

        return extra_user, extra_info

    @action(methods=['POST'], permission_classes=[AllowAny])
    def flag(self, request, pk=None):
        self.kwargs[self.lookup_field] = pk
        self.get_object()  # Will check that the Review instance is valid.
        request._request.CORS = RatingFlagViewSet.cors_allowed_methods
        view = RatingFlagViewSet.as_view({'post': 'create'})
        return view(request, *self.args, **{'review': pk})
コード例 #18
0
 def test_has_object_permission_partial_fail(self):
     request = RequestFactory().get('/')
     ok_(not AnyOf(FailPartialPermission, PartialFailPermission)
         ().has_object_permission(request, 'myview', None))
コード例 #19
0
 def test_has_permission_fail(self):
     request = RequestFactory().get('/')
     ok_(not AnyOf(AllowNone, AllowNone)
         ().has_permission(request, 'myview'))
コード例 #20
0
ファイル: views.py プロジェクト: varunnaganathan/zamboni
class AppViewSet(CORSMixin, SlugOrIdMixin, MarketplaceView,
                 viewsets.ModelViewSet):
    serializer_class = AppSerializer
    slug_field = 'app_slug'
    cors_allowed_methods = ('get', 'put', 'post', 'delete')
    permission_classes = [AnyOf(AllowAppOwner, AllowReviewerReadOnly,
                                AllowReadOnlyIfPublic)]
    authentication_classes = [RestOAuthAuthentication,
                              RestSharedSecretAuthentication,
                              RestAnonymousAuthentication]

    def get_queryset(self):
        return Webapp.objects.all().exclude(
            id__in=get_excluded_in(get_region().id))

    def get_base_queryset(self):
        return Webapp.objects.all()

    def get_object(self):
        try:
            app = super(AppViewSet, self).get_object()
        except Http404:
            self.get_queryset = self.get_base_queryset
            app = super(AppViewSet, self).get_object()
            # Owners and reviewers can see apps regardless of region.
            owner_or_reviewer = AnyOf(AllowAppOwner, AllowReviewerReadOnly)
            if owner_or_reviewer.has_object_permission(self.request, self,
                                                       app):
                return app
            data = {}
            for key in ('name', 'support_email', 'support_url'):
                value = getattr(app, key)
                data[key] = unicode(value) if value else ''
            data['reason'] = 'Not available in your region.'
            raise HttpLegallyUnavailable(data)
        self.check_object_permissions(self.request, app)
        return app

    def create(self, request, *args, **kwargs):
        uuid = request.data.get('upload', '')
        if uuid:
            is_packaged = True
        else:
            uuid = request.data.get('manifest', '')
            is_packaged = False
        if not uuid:
            raise exceptions.ParseError(
                'No upload or manifest specified.')

        try:
            upload = FileUpload.objects.get(uuid=uuid)
        except FileUpload.DoesNotExist:
            raise exceptions.ParseError('No upload found.')
        if not upload.valid:
            raise exceptions.ParseError('Upload not valid.')

        if not request.user.read_dev_agreement:
            log.info(u'Attempt to use API without dev agreement: %s'
                     % request.user.pk)
            raise exceptions.PermissionDenied('Terms of Service not accepted.')
        if not (upload.user and upload.user.pk == request.user.pk):
            raise exceptions.PermissionDenied('You do not own that app.')

        # Create app, user and fetch the icon.
        try:
            obj = Webapp.from_upload(upload, is_packaged=is_packaged)
        except (serializers.ValidationError,
                django_forms.ValidationError) as e:
            raise exceptions.ParseError(unicode(e))
        AddonUser(addon=obj, user=request.user).save()
        tasks.fetch_icon.delay(obj.pk, obj.latest_version.all_files[0].pk)
        record_action('app-submitted', request, {'app-id': obj.pk})
        log.info('App created: %s' % obj.pk)
        data = AppSerializer(
            context=self.get_serializer_context(), instance=obj).data

        return response.Response(
            data, status=201,
            headers={'Location': reverse('app-detail', kwargs={'pk': obj.pk})})

    def update(self, request, *args, **kwargs):
        # Fail if the app doesn't exist yet.
        self.get_object()
        r = super(AppViewSet, self).update(request, *args, **kwargs)
        # Be compatible with tastypie responses.
        if r.status_code == 200:
            r.status_code = 202
        return r

    def list(self, request, *args, **kwargs):
        if not request.user.is_authenticated():
            log.info('Anonymous listing not allowed')
            raise exceptions.PermissionDenied('Anonymous listing not allowed.')

        self.object_list = self.filter_queryset(self.get_queryset().filter(
            authors=request.user))
        page = self.paginate_queryset(self.object_list)
        serializer = self.get_serializer(page, many=True)
        return self.get_paginated_response(serializer.data)

    def partial_update(self, request, *args, **kwargs):
        raise exceptions.MethodNotAllowed('PATCH')

    @detail_route(methods=['POST'])
    def content_ratings(self, request, *args, **kwargs):
        app = self.get_object()
        form = IARCGetAppInfoForm(data=request.data, app=app)

        if form.is_valid():
            try:
                form.save(app)
                return Response(status=status.HTTP_201_CREATED)
            except django_forms.ValidationError:
                pass

        return Response(form.errors, status=status.HTTP_400_BAD_REQUEST)

    @detail_route(
        methods=['POST'],
        cors_allowed_methods=PreviewViewSet.cors_allowed_methods)
    def preview(self, request, *args, **kwargs):
        kwargs['app'] = self.get_object()
        view = PreviewViewSet.as_view({'post': '_create'})
        return view(request, *args, **kwargs)

    @detail_route(methods=['PUT'], cors_allowed_methods=['put'])
    def icon(self, request, *args, **kwargs):
        app = self.get_object()

        data_form = IconJSONForm(request.data)
        if not data_form.is_valid():
            return Response(data_form.errors,
                            status=status.HTTP_400_BAD_REQUEST)

        form = AppFormMedia(data_form.cleaned_data, request=request)
        if not form.is_valid():
            return Response(data_form.errors,
                            status=status.HTTP_400_BAD_REQUEST)

        form.save(app)
        return Response(status=status.HTTP_200_OK)