Exemple #1
0
class APIView(View):

    @method_decorator(csrf_exempt)
    @method_decorator(require_valid_user)
    @method_decorator(check_username)
    @method_decorator(never_cache)
    @method_decorator(cors_origin())
    def dispatch(self, *args, **kwargs):
        """ Dispatches request and does generic error handling """
        try:
            return super(APIView, self).dispatch(*args, **kwargs)

        except ObjectDoesNotExist as e:
            return HttpResponseNotFound(str(e))

        except (RequestException, ParameterMissing) as e:
            return HttpResponseBadRequest(str(e))

    def parsed_body(self, request):
        """ Returns the object parsed from the JSON request body """

        if not request.body:
            raise RequestException('POST data must not be empty')

        try:
            # TODO: implementation of parse_request_body can be moved here
            # after all views using it have been refactored
            return parse_request_body(request)
        except (UnicodeDecodeError, ValueError) as e:
            msg = 'Could not decode request body for user {}: {}'.format(
                request.user.username,
                request.body.decode('ascii', errors='replace'))
            logger.warn(msg, exc_info=True)
            raise RequestException(msg)

    def get_since(self, request):
        """ Returns parsed "since" GET parameter """
        since_ = request.GET.get('since', None)

        if since_ is None:
            raise RequestException("parameter 'since' missing")

        try:
            since_ = int(since_)
            since = datetime.fromtimestamp(since_)
        except ValueError:
            raise RequestException("'since' is not a valid timestamp")

        if since_ < 0:
            raise RequestException("'since' must be a non-negative number")

        return since
Exemple #2
0
class DeviceUpdates(View):
    """returns various updates for a device

    https://gpoddernet.readthedocs.io/en/latest/api//Devices#Get_Updates"""
    @method_decorator(csrf_exempt)
    @method_decorator(require_valid_user)
    @method_decorator(check_username)
    @method_decorator(never_cache)
    @method_decorator(cors_origin())
    def get(self, request, username, device_uid):

        now = datetime.utcnow()
        now_ = get_timestamp(now)

        user = request.user

        try:
            device = user.client_set.get(uid=device_uid)
        except Client.DoesNotExist as e:
            return HttpResponseNotFound(str(e))

        try:
            since = self.get_since(request)
        except ValueError as e:
            return HttpResponseBadRequest(str(e))

        include_actions = parse_bool(request.GET.get("include_actions", False))

        domain = RequestSite(request).domain

        add, rem, subscriptions = self.get_subscription_changes(
            user, device, since, now, domain)
        updates = self.get_episode_changes(user, subscriptions, domain,
                                           include_actions, since)

        return JsonResponse({
            "add": add,
            "rem": rem,
            "updates": updates,
            "timestamp": get_timestamp(now),
        })

    def get_subscription_changes(self, user, device, since, now, domain):
        """gets new, removed and current subscriptions"""

        history = get_subscription_history(user, device, since, now)
        add, rem = subscription_diff(history)

        subscriptions = device.get_subscribed_podcasts()

        add = [podcast_data(p, domain) for p in add]
        rem = [p.url for p in rem]

        return add, rem, subscriptions

    def get_episode_changes(self, user, subscriptions, domain, include_actions,
                            since):
        devices = {dev.id.hex: dev.uid for dev in user.client_set.all()}

        # index subscribed podcasts by their Id for fast access
        podcasts = {p.get_id(): p for p in subscriptions}

        episode_updates = self.get_episode_updates(user, subscriptions, since)

        return [
            self.get_episode_data(status, podcasts, domain, include_actions,
                                  user, devices) for status in episode_updates
        ]

    def get_episode_updates(self,
                            user,
                            subscribed_podcasts,
                            since,
                            max_per_podcast=5):
        """Returns the episode updates since the timestamp"""

        episodes = []
        for podcast in subscribed_podcasts:
            eps = Episode.objects.filter(podcast=podcast,
                                         released__gt=since).order_by(
                                             "-order", "-released")
            episodes.extend(eps[:max_per_podcast])

        states = EpisodeState.dict_for_user(user, episodes)

        for episode in episodes:
            yield EpisodeStatus(episode, states.get(episode.id, "new"), None)

    def get_episode_data(self, episode_status, podcasts, domain,
                         include_actions, user, devices):
        """Get episode data for an episode status object"""

        # TODO: shouldn't the podcast_id be in the episode status?
        podcast_id = episode_status.episode.podcast
        podcast = podcasts.get(podcast_id, None)
        t = episode_data(episode_status.episode, domain, podcast)
        t["status"] = episode_status.status

        # include latest action (bug 1419)
        # TODO
        if include_actions and episode_status.action:
            t["action"] = episode_action_json(episode_status.action, user)

        return t

    def get_since(self, request):
        """parses the "since" parameter"""
        since_ = request.GET.get("since", None)
        if since_ is None:
            raise ValueError("parameter since missing")
        try:
            return datetime.fromtimestamp(float(since_))
        except ValueError as e:
            raise ValueError("'since' is not a valid timestamp: %s" % str(e))