{"action":"created","comment":{"url":"https://api.github.com/repos/allegro/ralph_pricing/pulls/comments/78539895","id":78539895,"diff_hunk":"@@ -0,0 +1,335 @@\n+# -*- coding: utf-8 -*-\n+\n+from __future__ import absolute_import\n+from __future__ import division\n+from __future__ import print_function\n+from __future__ import unicode_literals\n+\n+import datetime\n+import json\n+import logging\n+from collections import defaultdict\n+\n+from django.http import HttpResponse\n+from rest_framework import status\n+from rest_framework import serializers\n+from rest_framework.views import APIView\n+from rest_framework.response import Response\n+from rest_framework.serializers import Serializer\n+\n+from ralph_scrooge.models import (\n+    DailyUsage,\n+    PricingObject,\n+    PricingService,\n+    ServiceEnvironment,\n+    UsageType,\n+)\n+\n+logger = logging.getLogger(__name__)\n+\n+\n+class UsageObject(object):\n+    symbol = None\n+    value = None\n+\n+    def __init__(self, symbol=None, value=None, **kwargs):\n+        self.symbol = symbol\n+        self.value = value\n+\n+\n+class UsageObjectSerializer(Serializer):\n+    symbol = serializers.CharField()\n+    value = serializers.FloatField()\n+\n+    class Meta:\n+        model = UsageObject\n+\n+\n+class UsageObjectDeserializer(UsageObjectSerializer):\n+    \"\"\"Dummy class provided only for symmetry.\"\"\"\n+    pass\n+\n+\n+class UsagesObject(object):\n+    service = None\n+    service_id = None\n+    service_uid = None\n+    environment = None\n+    pricing_object = None\n+    usages = []  # list of UsageObject\n+\n+    def __init__(\n+        self,\n+        service=None,\n+        service_id=None,\n+        service_uid=None,\n+        environment=None,\n+        pricing_object=None,\n+        usages=None,\n+        **kwargs\n+    ):\n+        self.service = service\n+        self.service_id = service_id\n+        self.service_uid = service_uid\n+        self.environment = environment\n+        self.pricing_object = pricing_object\n+        self.usages = usages or []\n+\n+\n+class UsagesObjectSerializer(Serializer):\n+    service = serializers.CharField()\n+    service_id = serializers.IntegerField()\n+    service_uid = serializers.CharField()\n+    environment = serializers.CharField()\n+    pricing_object = serializers.CharField()\n+    usages = UsageObjectSerializer(many=True)\n+\n+    class Meta:\n+        model = UsagesObject\n+        exclude = ('service_uid',)\n+\n+\n+class UsagesObjectDeserializer(UsagesObjectSerializer):\n+    service = serializers.CharField(required=False)\n+    service_id = serializers.IntegerField(required=False)\n+    service_uid = serializers.CharField(required=False)\n+    environment = serializers.CharField(required=False)\n+    pricing_object = serializers.CharField(required=False)\n+    usages = UsageObjectDeserializer(many=True, required=False)\n+\n+    def validate(self, usages_obj):\n+        # TODO(xor-xor): Get rid of \"non_field_errors\" header from error\n+        # message generated by DjRF.\n+        pricing_obj = usages_obj.get('pricing_object')\n+        service = usages_obj.get('service')\n+        service_id = usages_obj.get('service_id')\n+        service_uid = usages_obj.get('service_uid')\n+        env = usages_obj.get('environment')\n+        err = None\n+\n+        if pricing_obj and any((service, service_id, service_uid, env)):\n+            err = (\"pricing_object shouldn't be used with any of: \"\n+                    \"service, service_id, service_uid, environment\")\n+        elif service and any((service_id, service_uid)):\n+            err = (\"service shouldn't be used with any of: \"\n+                    \"service_id, service_uid\")\n+        elif service_id and service_uid:\n+            err = \"service_id shouldn't be used with service_uid\"\n+        elif env and not any((service, service_id, service_uid)):\n+            err = (\"environment requires service or service_id or \"\n+                    \"service_uid\")\n+\n+        if err is not None:\n+            msg = \"Invalid combination of fields: {}.\".format(err)\n+            raise serializers.ValidationError(msg)\n+\n+    class Meta:\n+        model = UsagesObject\n+\n+\n+class PricingServiceUsageObject(object):\n+    pricing_service = None\n+    pricing_service_id = None\n+    date = None\n+    overwrite = None\n+    usages = []  # list of UsagesObject\n+\n+    def __init__(\n+        self,\n+        pricing_service=None,\n+        pricing_service_id=None,\n+        usages=None,\n+        date=None,\n+        **kwargs\n+    ):\n+        self.pricing_service = pricing_service\n+        self.pricing_service_id = pricing_service_id\n+        self.date = date\n+        self.usages = usages or []\n+        self.overwrite = 'no'  # overwrite by default\n+\n+\n+class PricingServiceUsageObjectSerializer(Serializer):\n+    pricing_service = serializers.CharField()\n+    pricing_service_id = serializers.IntegerField()\n+    date = serializers.DateField()\n+    overwrite = serializers.CharField()\n+    usages = UsagesObjectSerializer(many=True)\n+\n+    class Meta:\n+        model = PricingServiceUsageObject\n+        exclude = ('overwrite',)\n+\n+\n+class PricingServiceUsageObjectDeserializer(PricingServiceUsageObjectSerializer):  # noqa\n+    pricing_service_id = serializers.CharField(required=False)\n+    overwrite = serializers.CharField(required=False)\n+    usages = UsagesObjectDeserializer(many=True)\n+\n+    class Meta:\n+        model = PricingServiceUsageObject\n+\n+\n+class PricingServiceUsages(APIView):\n+\n+    # def validate(self, post_data):  # XXX move this logic to deserializer\n+    #     # check if service exists\n+    #     ps_params = {}\n+    #     if post_data.get('pricing_service_id'):\n+    #         ps_params['id'] = post_data['pricing_service_id']\n+    #     elif post_data.get('pricing_service'):\n+    #         ps_params['name'] = post_data['pricing_service']\n+    #         PricingService.objects_admin.get(**ps_params)  # XXX try/except\n+\n+    #     # check if date is properly set\n+    #     # assert isinstance(post_data['date'], datetime.date)  # XXX\n+\n+    #     # check if overwrite is properly set\n+    #     assert post_data.get('overwrite', 'no') in (  # XXX\n+    #         'no',\n+    #         'delete_all_previous',\n+    #         'values_only',\n+    #     )\n+\n+    def get(self, request, *args, **kwargs):\n+        usages_date = datetime.datetime.strptime(\n+            kwargs['date'],\n+            '%Y-%m-%d'\n+        ).date()\n+        pricing_service_id = kwargs['pricing_service_id']\n+        result = self.get_as_obj(usages_date, pricing_service_id)\n+        return Response(PricingServiceUsageObjectSerializer(result).data)\n+\n+    def post(self, request, *args, **kwargs):\n+        post_data = json.loads(request.raw_post_data)  # XXX what about using BytesIO/JSONParser for that..?\n+        deserializer = PricingServiceUsageObjectDeserializer(data=post_data)","path":"src/ralph_scrooge/rest/pricing_service_usages.py","position":205,"original_position":205,"commit_id":"af8ddd2f1443c1a487a292f8bb8a2b2346e3792c","original_commit_id":"af8ddd2f1443c1a487a292f8bb8a2b2346e3792c","user":{"login":"xor-xor","id":2082842,"avatar_url":"https://avatars.githubusercontent.com/u/2082842?v=3","gravatar_id":"","url":"https://api.github.com/users/xor-xor","html_url":"https://github.com/xor-xor","followers_url":"https://api.github.com/users/xor-xor/followers","following_url":"https://api.github.com/users/xor-xor/following{/other_user}","gists_url":"https://api.github.com/users/xor-xor/gists{/gist_id}","starred_url":"https://api.github.com/users/xor-xor/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xor-xor/subscriptions","organizations_url":"https://api.github.com/users/xor-xor/orgs","repos_url":"https://api.github.com/users/xor-xor/repos","events_url":"https://api.github.com/users/xor-xor/events{/privacy}","received_events_url":"https://api.github.com/users/xor-xor/received_events","type":"User","site_admin":false},"body":"Done.","created_at":"2016-09-13T11:23:41Z","updated_at":"2016-09-13T11:23:41Z","html_url":"https://github.com/allegro/ralph_pricing/pull/507#discussion_r78539895","pull_request_url":"https://api.github.com/repos/allegro/ralph_pricing/pulls/507","_links":{"self":{"href":"https://api.github.com/repos/allegro/ralph_pricing/pulls/comments/78539895"},"html":{"href":"https://github.com/allegro/ralph_pricing/pull/507#discussion_r78539895"},"pull_request":{"href":"https://api.github.com/repos/allegro/ralph_pricing/pulls/507"}}},"pull_request":{"url":"https://api.github.com/repos/allegro/ralph_pricing/pulls/507","id":84881401,"html_url":"https://github.com/allegro/ralph_pricing/pull/507","diff_url":"https://github.com/allegro/ralph_pricing/pull/507.diff","patch_url":"https://github.com/allegro/ralph_pricing/pull/507.patch","issue_url":"https://api.github.com/repos/allegro/ralph_pricing/issues/507","number":507,"state":"open","locked":false,"title":"Migrate API to DjRF","user":{"login":"xor-xor","id":2082842,"avatar_url":"https://avatars.githubusercontent.com/u/2082842?v=3","gravatar_id":"","url":"https://api.github.com/users/xor-xor","html_url":"https://github.com/xor-xor","followers_url":"https://api.github.com/users/xor-xor/followers","following_url":"https://api.github.com/users/xor-xor/following{/other_user}","gists_url":"https://api.github.com/users/xor-xor/gists{/gist_id}","starred_url":"https://api.github.com/users/xor-xor/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xor-xor/subscriptions","organizations_url":"https://api.github.com/users/xor-xor/orgs","repos_url":"https://api.github.com/users/xor-xor/repos","events_url":"https://api.github.com/users/xor-xor/events{/privacy}","received_events_url":"https://api.github.com/users/xor-xor/received_events","type":"User","site_admin":false},"body":"Preview only! Do not merge yet!","created_at":"2016-09-12T14:29:31Z","updated_at":"2016-09-13T11:23:41Z","closed_at":null,"merged_at":null,"merge_commit_sha":"3383261ed3e5a7c06c05d8f05a2857d3e16f6858","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/allegro/ralph_pricing/pulls/507/commits","review_comments_url":"https://api.github.com/repos/allegro/ralph_pricing/pulls/507/comments","review_comment_url":"https://api.github.com/repos/allegro/ralph_pricing/pulls/comments{/number}","comments_url":"https://api.github.com/repos/allegro/ralph_pricing/issues/507/comments","statuses_url":"https://api.github.com/repos/allegro/ralph_pricing/statuses/af8ddd2f1443c1a487a292f8bb8a2b2346e3792c","head":{"label":"xor-xor:api","ref":"api","sha":"af8ddd2f1443c1a487a292f8bb8a2b2346e3792c","user":{"login":"xor-xor","id":2082842,"avatar_url":"https://avatars.githubusercontent.com/u/2082842?v=3","gravatar_id":"","url":"https://api.github.com/users/xor-xor","html_url":"https://github.com/xor-xor","followers_url":"https://api.github.com/users/xor-xor/followers","following_url":"https://api.github.com/users/xor-xor/following{/other_user}","gists_url":"https://api.github.com/users/xor-xor/gists{/gist_id}","starred_url":"https://api.github.com/users/xor-xor/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xor-xor/subscriptions","organizations_url":"https://api.github.com/users/xor-xor/orgs","repos_url":"https://api.github.com/users/xor-xor/repos","events_url":"https://api.github.com/users/xor-xor/events{/privacy}","received_events_url":"https://api.github.com/users/xor-xor/received_events","type":"User","site_admin":false},"repo":{"id":64742322,"name":"ralph_pricing","full_name":"xor-xor/ralph_pricing","owner":{"login":"xor-xor","id":2082842,"avatar_url":"https://avatars.githubusercontent.com/u/2082842?v=3","gravatar_id":"","url":"https://api.github.com/users/xor-xor","html_url":"https://github.com/xor-xor","followers_url":"https://api.github.com/users/xor-xor/followers","following_url":"https://api.github.com/users/xor-xor/following{/other_user}","gists_url":"https://api.github.com/users/xor-xor/gists{/gist_id}","starred_url":"https://api.github.com/users/xor-xor/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xor-xor/subscriptions","organizations_url":"https://api.github.com/users/xor-xor/orgs","repos_url":"https://api.github.com/users/xor-xor/repos","events_url":"https://api.github.com/users/xor-xor/events{/privacy}","received_events_url":"https://api.github.com/users/xor-xor/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/xor-xor/ralph_pricing","description":"A pricing module for Ralph","fork":true,"url":"https://api.github.com/repos/xor-xor/ralph_pricing","forks_url":"https://api.github.com/repos/xor-xor/ralph_pricing/forks","keys_url":"https://api.github.com/repos/xor-xor/ralph_pricing/keys{/key_id}","collaborators_url":"https://api.github.com/repos/xor-xor/ralph_pricing/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/xor-xor/ralph_pricing/teams","hooks_url":"https://api.github.com/repos/xor-xor/ralph_pricing/hooks","issue_events_url":"https://api.github.com/repos/xor-xor/ralph_pricing/issues/events{/number}","events_url":"https://api.github.com/repos/xor-xor/ralph_pricing/events","assignees_url":"https://api.github.com/repos/xor-xor/ralph_pricing/assignees{/user}","branches_url":"https://api.github.com/repos/xor-xor/ralph_pricing/branches{/branch}","tags_url":"https://api.github.com/repos/xor-xor/ralph_pricing/tags","blobs_url":"https://api.github.com/repos/xor-xor/ralph_pricing/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/xor-xor/ralph_pricing/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/xor-xor/ralph_pricing/git/refs{/sha}","trees_url":"https://api.github.com/repos/xor-xor/ralph_pricing/git/trees{/sha}","statuses_url":"https://api.github.com/repos/xor-xor/ralph_pricing/statuses/{sha}","languages_url":"https://api.github.com/repos/xor-xor/ralph_pricing/languages","stargazers_url":"https://api.github.com/repos/xor-xor/ralph_pricing/stargazers","contributors_url":"https://api.github.com/repos/xor-xor/ralph_pricing/contributors","subscribers_url":"https://api.github.com/repos/xor-xor/ralph_pricing/subscribers","subscription_url":"https://api.github.com/repos/xor-xor/ralph_pricing/subscription","commits_url":"https://api.github.com/repos/xor-xor/ralph_pricing/commits{/sha}","git_commits_url":"https://api.github.com/repos/xor-xor/ralph_pricing/git/commits{/sha}","comments_url":"https://api.github.com/repos/xor-xor/ralph_pricing/comments{/number}","issue_comment_url":"https://api.github.com/repos/xor-xor/ralph_pricing/issues/comments{/number}","contents_url":"https://api.github.com/repos/xor-xor/ralph_pricing/contents/{+path}","compare_url":"https://api.github.com/repos/xor-xor/ralph_pricing/compare/{base}...{head}","merges_url":"https://api.github.com/repos/xor-xor/ralph_pricing/merges","archive_url":"https://api.github.com/repos/xor-xor/ralph_pricing/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/xor-xor/ralph_pricing/downloads","issues_url":"https://api.github.com/repos/xor-xor/ralph_pricing/issues{/number}","pulls_url":"https://api.github.com/repos/xor-xor/ralph_pricing/pulls{/number}","milestones_url":"https://api.github.com/repos/xor-xor/ralph_pricing/milestones{/number}","notifications_url":"https://api.github.com/repos/xor-xor/ralph_pricing/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/xor-xor/ralph_pricing/labels{/name}","releases_url":"https://api.github.com/repos/xor-xor/ralph_pricing/releases{/id}","deployments_url":"https://api.github.com/repos/xor-xor/ralph_pricing/deployments","created_at":"2016-08-02T09:12:33Z","updated_at":"2016-08-02T09:12:34Z","pushed_at":"2016-09-13T08:55:37Z","git_url":"git://github.com/xor-xor/ralph_pricing.git","ssh_url":"git@github.com:xor-xor/ralph_pricing.git","clone_url":"https://github.com/xor-xor/ralph_pricing.git","svn_url":"https://github.com/xor-xor/ralph_pricing","homepage":null,"size":5339,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"develop"}},"base":{"label":"allegro:develop","ref":"develop","sha":"26dc89ac6739263c650face37b2efb1a74757c35","user":{"login":"allegro","id":562236,"avatar_url":"https://avatars.githubusercontent.com/u/562236?v=3","gravatar_id":"","url":"https://api.github.com/users/allegro","html_url":"https://github.com/allegro","followers_url":"https://api.github.com/users/allegro/followers","following_url":"https://api.github.com/users/allegro/following{/other_user}","gists_url":"https://api.github.com/users/allegro/gists{/gist_id}","starred_url":"https://api.github.com/users/allegro/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/allegro/subscriptions","organizations_url":"https://api.github.com/users/allegro/orgs","repos_url":"https://api.github.com/users/allegro/repos","events_url":"https://api.github.com/users/allegro/events{/privacy}","received_events_url":"https://api.github.com/users/allegro/received_events","type":"Organization","site_admin":false},"repo":{"id":9495095,"name":"ralph_pricing","full_name":"allegro/ralph_pricing","owner":{"login":"allegro","id":562236,"avatar_url":"https://avatars.githubusercontent.com/u/562236?v=3","gravatar_id":"","url":"https://api.github.com/users/allegro","html_url":"https://github.com/allegro","followers_url":"https://api.github.com/users/allegro/followers","following_url":"https://api.github.com/users/allegro/following{/other_user}","gists_url":"https://api.github.com/users/allegro/gists{/gist_id}","starred_url":"https://api.github.com/users/allegro/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/allegro/subscriptions","organizations_url":"https://api.github.com/users/allegro/orgs","repos_url":"https://api.github.com/users/allegro/repos","events_url":"https://api.github.com/users/allegro/events{/privacy}","received_events_url":"https://api.github.com/users/allegro/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/allegro/ralph_pricing","description":"A pricing module for Ralph","fork":false,"url":"https://api.github.com/repos/allegro/ralph_pricing","forks_url":"https://api.github.com/repos/allegro/ralph_pricing/forks","keys_url":"https://api.github.com/repos/allegro/ralph_pricing/keys{/key_id}","collaborators_url":"https://api.github.com/repos/allegro/ralph_pricing/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/allegro/ralph_pricing/teams","hooks_url":"https://api.github.com/repos/allegro/ralph_pricing/hooks","issue_events_url":"https://api.github.com/repos/allegro/ralph_pricing/issues/events{/number}","events_url":"https://api.github.com/repos/allegro/ralph_pricing/events","assignees_url":"https://api.github.com/repos/allegro/ralph_pricing/assignees{/user}","branches_url":"https://api.github.com/repos/allegro/ralph_pricing/branches{/branch}","tags_url":"https://api.github.com/repos/allegro/ralph_pricing/tags","blobs_url":"https://api.github.com/repos/allegro/ralph_pricing/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/allegro/ralph_pricing/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/allegro/ralph_pricing/git/refs{/sha}","trees_url":"https://api.github.com/repos/allegro/ralph_pricing/git/trees{/sha}","statuses_url":"https://api.github.com/repos/allegro/ralph_pricing/statuses/{sha}","languages_url":"https://api.github.com/repos/allegro/ralph_pricing/languages","stargazers_url":"https://api.github.com/repos/allegro/ralph_pricing/stargazers","contributors_url":"https://api.github.com/repos/allegro/ralph_pricing/contributors","subscribers_url":"https://api.github.com/repos/allegro/ralph_pricing/subscribers","subscription_url":"https://api.github.com/repos/allegro/ralph_pricing/subscription","commits_url":"https://api.github.com/repos/allegro/ralph_pricing/commits{/sha}","git_commits_url":"https://api.github.com/repos/allegro/ralph_pricing/git/commits{/sha}","comments_url":"https://api.github.com/repos/allegro/ralph_pricing/comments{/number}","issue_comment_url":"https://api.github.com/repos/allegro/ralph_pricing/issues/comments{/number}","contents_url":"https://api.github.com/repos/allegro/ralph_pricing/contents/{+path}","compare_url":"https://api.github.com/repos/allegro/ralph_pricing/compare/{base}...{head}","merges_url":"https://api.github.com/repos/allegro/ralph_pricing/merges","archive_url":"https://api.github.com/repos/allegro/ralph_pricing/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/allegro/ralph_pricing/downloads","issues_url":"https://api.github.com/repos/allegro/ralph_pricing/issues{/number}","pulls_url":"https://api.github.com/repos/allegro/ralph_pricing/pulls{/number}","milestones_url":"https://api.github.com/repos/allegro/ralph_pricing/milestones{/number}","notifications_url":"https://api.github.com/repos/allegro/ralph_pricing/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/allegro/ralph_pricing/labels{/name}","releases_url":"https://api.github.com/repos/allegro/ralph_pricing/releases{/id}","deployments_url":"https://api.github.com/repos/allegro/ralph_pricing/deployments","created_at":"2013-04-17T10:30:06Z","updated_at":"2016-05-06T19:28:53Z","pushed_at":"2016-09-13T11:20:43Z","git_url":"git://github.com/allegro/ralph_pricing.git","ssh_url":"git@github.com:allegro/ralph_pricing.git","clone_url":"https://github.com/allegro/ralph_pricing.git","svn_url":"https://github.com/allegro/ralph_pricing","homepage":null,"size":6362,"stargazers_count":7,"watchers_count":7,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":17,"mirror_url":null,"open_issues_count":12,"forks":17,"open_issues":12,"watchers":7,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/allegro/ralph_pricing/pulls/507"},"html":{"href":"https://github.com/allegro/ralph_pricing/pull/507"},"issue":{"href":"https://api.github.com/repos/allegro/ralph_pricing/issues/507"},"comments":{"href":"https://api.github.com/repos/allegro/ralph_pricing/issues/507/comments"},"review_comments":{"href":"https://api.github.com/repos/allegro/ralph_pricing/pulls/507/comments"},"review_comment":{"href":"https://api.github.com/repos/allegro/ralph_pricing/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/allegro/ralph_pricing/pulls/507/commits"},"statuses":{"href":"https://api.github.com/repos/allegro/ralph_pricing/statuses/af8ddd2f1443c1a487a292f8bb8a2b2346e3792c"}}}}
{"actor":{"display_login":"xor-xor"}}