{"action":"created","comment":{"url":"https://api.github.com/repos/odoo/odoo/pulls/comments/46937661","id":46937661,"diff_hunk":"@@ -0,0 +1,1092 @@\n+# -*- coding: utf-8 -*-\n+# Part of Odoo. See LICENSE file for full copyright and licensing details.\n+\n+import babel.dates\n+import collections\n+import pytz\n+import re\n+import time\n+import uuid\n+from datetime import datetime, timedelta\n+from dateutil import parser\n+from dateutil import rrule\n+from dateutil.relativedelta import relativedelta\n+from operator import itemgetter\n+\n+import odoo\n+import odoo.service.report\n+from odoo import api, fields, models, tools, _\n+from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT\n+from odoo.exceptions import UserError\n+from odoo.addons.calendar.models.calendar_attendee import CalendarAttendee\n+\n+import logging\n+_logger = logging.getLogger(__name__)\n+\n+\n+def calendar_id2real_id(calendar_id=None, with_date=False):\n+    \"\"\"\n+    Convert a \"virtual/recurring event id\" (type string) into a real event id (type int).\n+    E.g. virtual/recurring event id is 4-20091201100000, so it will return 4.\n+    @param calendar_id: id of calendar\n+    @param with_date: if a value is passed to this param it will return dates based on value of withdate + calendar_id\n+    @return: real event id\n+    \"\"\"\n+    if calendar_id and isinstance(calendar_id, (basestring)):\n+        res = calendar_id.split('-')\n+        if len(res) >= 2:\n+            real_id = res[0]\n+            if with_date:\n+                real_date = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT, time.strptime(res[1], \"%Y%m%d%H%M%S\"))\n+                start = datetime.strptime(real_date, DEFAULT_SERVER_DATETIME_FORMAT)\n+                end = start + timedelta(hours=with_date)\n+                return (int(real_id), real_date, end.strftime(DEFAULT_SERVER_DATETIME_FORMAT))\n+            return int(real_id)\n+    return calendar_id and int(calendar_id) or calendar_id\n+\n+\n+def get_real_ids(ids):\n+    if isinstance(ids, (basestring, int, long)):\n+        t1 = calendar_id2real_id(ids)\n+        return t1\n+\n+    if isinstance(ids, (list, tuple)):\n+        t2 = [calendar_id2real_id(id) for id in ids]\n+        return t2\n+\n+\n+class CalendarEventType(models.Model):\n+    _name = 'calendar.event.type'\n+    _description = 'Meeting Type'\n+\n+    name = fields.Char(required=True)\n+\n+    _sql_constraints = [('name_uniq', 'unique (name)', \"Tag name already exists !\")]\n+\n+\n+class CalendarEvent(models.Model):\n+    \"\"\" Model for Calendar Event \"\"\"\n+    _name = 'calendar.event'\n+    _description = \"Event\"\n+    _order = \"id desc\"\n+    _inherit = [\"mail.thread\", \"ir.needaction_mixin\"]\n+\n+    def do_run_scheduler(self):\n+        self.env['calendar.alarm_manager'].get_next_mail()\n+\n+    @api.multi\n+    def get_recurrent_date_by_event(self):\n+        \"\"\"Get recurrent dates based on Rule string and all event where recurrent_id is child\n+        \"\"\"\n+\n+        def todate(date):\n+            val = parser.parse(''.join((re.compile('\\d')).findall(date)))\n+            ## Dates are localized to saved timezone if any, else current timezone.\n+            if not val.tzinfo:\n+                val = pytz.UTC.localize(val)\n+            return val.astimezone(timezone)\n+\n+        timezone = pytz.timezone(self.env.context.get('tz') or 'UTC')\n+        startdate = pytz.UTC.localize(fields.Datetime.from_string(self.start))  # Add \"+hh:mm\" timezone\n+        if not startdate:\n+            startdate = datetime.now()\n+\n+        ## Convert the start date to saved timezone (or context tz) as it'll\n+        ## define the correct hour/day asked by the user to repeat for recurrence.\n+        startdate = startdate.astimezone(timezone)  # transform \"+hh:mm\" timezone\n+        rset1 = rrule.rrulestr(str(self.rrule), dtstart=startdate, forceset=True)\n+        all_events = self.search([('recurrent_id', '=', self.id), '|', ('active', '=', False), ('active', '=', True)])\n+        for ev in all_events:\n+            rset1._exdate.append(todate(ev.recurrent_id_date))\n+        return [d.astimezone(pytz.UTC) for d in rset1]\n+\n+    @api.multi\n+    def _get_recurrency_end_date(self):\n+        data = self.read(['final_date', 'recurrency', 'rrule_type', 'count', 'end_type', 'stop'])[0]\n+        if not data.get('recurrency'):\n+            return False\n+\n+        end_type = data.get('end_type')\n+        final_date = data.get('final_date')\n+        if end_type == 'count' and all(data.get(key) for key in ['count', 'rrule_type', 'stop']):\n+            count = data['count'] + 1\n+            delay, mult = {\n+                'daily': ('days', 1),\n+                'weekly': ('days', 7),\n+                'monthly': ('months', 1),\n+                'yearly': ('years', 1),\n+            }[data['rrule_type']]\n+\n+            deadline = fields.Datetime.from_string(data['stop'])\n+            return deadline + relativedelta(**{delay: count * mult})\n+        return final_date\n+\n+    @api.multi\n+    def _find_my_attendee(self):\n+        \"\"\"\n+            Return the first attendee where the user connected has been invited from all the meeting_ids in parameters\n+        \"\"\"\n+        for event in self:\n+            for attendee in event.attendee_ids:\n+                if self.env.user.partner_id.id == attendee.partner_id.id:\n+                    return attendee\n+        return False\n+\n+    @api.multi\n+    def get_date_formats(self):\n+        lang = self.env.lang\n+        lang_params = {}\n+        if lang:\n+            res_lang = self.env['res.lang'].search([(\"code\", \"=\", lang)])\n+            if res_lang:\n+                lang_params = res_lang.read([\"date_format\", \"time_format\"])\n+\n+        # formats will be used for str{f,p}time() which do not support unicode in Python 2, coerce to str\n+        format_date = lang_params.get(\"date_format\", '%B-%d-%Y').encode('utf-8')\n+        format_time = lang_params.get(\"time_format\", '%I-%M %p').encode('utf-8')\n+        return (format_date, format_time)\n+\n+    @api.multi\n+    def get_display_time_tz(self, tz=False):\n+        if tz:\n+            self.env.context[\"tz\"] = tz\n+        event = self[0]\n+        return self._get_display_time(event.start, event.stop, event.duration, event.allday)\n+\n+    @api.model\n+    def _get_display_time(self, start, stop, zduration, zallday):\n+        \"\"\"\n+            Return date and time (from to from) based on duration with timezone in string :\n+            eg.\n+            1) if user add duration for 2 hours, return : August-23-2013 at (04-30 To 06-30) (Europe/Brussels)\n+            2) if event all day ,return : AllDay, July-31-2013\n+        \"\"\"\n+        tz = self.env.context.get('tz')\n+        if not tz:  # tz can have a value False, so dont do it in the default value of get !\n+            tz = self.env.context.get('tz', self.env.user.tz)\n+        tz = tools.ustr(tz).encode('utf-8')  # make safe for str{p,f}time()\n+\n+        format_date, format_time = self.with_context(tz=self.env.user.tz).get_date_formats()\n+        date = fields.Datetime.context_timestamp(self.with_context(tz=self.env.user.tz), fields.Datetime.from_string(start))\n+        date_deadline = fields.Datetime.context_timestamp(self.with_context(tz=self.env.user.tz), fields.Datetime.from_string(stop))\n+        event_date = date.strftime(format_date)\n+        display_time = date.strftime(format_time)\n+\n+        if zallday:\n+            time = _(\"AllDay , %s\") % (event_date)\n+        elif zduration < 24:\n+            duration = date + timedelta(hours=zduration)\n+            time = _(\"%s at (%s To %s) (%s)\") % (event_date, display_time, duration.strftime(format_time), tz)\n+        else:\n+            time = _(\"%s at %s To\\n %s at %s (%s)\") % (event_date, display_time, date_deadline.strftime(format_date), date_deadline.strftime(format_time), tz)\n+        return time\n+\n+    def _compute_is_attendee(self):\n+        for meeting in self:\n+            attendee = meeting._find_my_attendee()\n+            meeting.is_attendee = bool(attendee)\n+\n+    def _compute_attendee_status(self):\n+        for meeting in self:\n+            attendee = meeting._find_my_attendee()\n+            meeting.attendee_status = attendee.state if attendee else 'needsAction'\n+\n+    def _compute_display_time(self):\n+        for meeting in self:\n+            meeting.display_time = self._get_display_time(meeting.start, meeting.stop, meeting.duration, meeting.allday)\n+\n+    def _compute_display_start(self):\n+        for meeting in self:\n+            meeting.display_start = meeting.start_date if meeting.allday else meeting.start_datetime\n+\n+    def _compute_start(self):\n+        for meeting in self:\n+            meeting.start = meeting.start_date if meeting.allday else meeting.start_datetime\n+\n+    def _compute_stop(self):\n+        for meeting in self:\n+            meeting.stop = meeting.stop_date if meeting.allday else meeting.stop_datetime\n+\n+    @api.depends('final_date')\n+    def _get_rulestring(self):\n+        \"\"\"\n+        Gets Recurrence rule string according to value type RECUR of iCalendar from the values given.\n+        @return: dictionary of rrule value.\n+        \"\"\"\n+        #read these fields as SUPERUSER because if the record is private a normal search could raise an error\n+        events = self.sudo().read(['id', 'byday', 'recurrency', 'final_date', 'rrule_type', 'month_by',\n+                            'interval', 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr', 'sa',\n+                            'su', 'day', 'week_list'])\n+        for event in events:\n+            if event['recurrency']:\n+                rrule = self.compute_rule_string(event)\n+                self.browse(event['id']).rrule = rrule\n+            else:\n+                self.browse(event['id']).rrule = ''\n+\n+    # retro compatibility function\n+    def _rrule_write(self):\n+        return self._set_rulestring(self)\n+\n+    @api.multi\n+    def _set_rulestring(self):\n+        data = self._get_empty_rrule_data()\n+        if self.rrule:\n+            data['recurrency'] = True\n+            for event in self:\n+                rdate = event.start\n+                update_data = self._parse_rrule(self.rrule, dict(data), rdate)\n+                data.update(update_data)\n+                event.write(data)\n+        return True\n+\n+    @api.model\n+    def _set_date(self, values, id=False):\n+\n+        if values.get('start_datetime') or values.get('start_date') or values.get('start') \\\n+                or values.get('stop_datetime') or values.get('stop_date') or values.get('stop'):\n+            allday = values.get(\"allday\", None)\n+            event = self.browse(id)\n+            if allday is None:\n+                if id:\n+                    allday = event.allday\n+                else:\n+                    allday = False\n+                    _logger.debug(\"Calendar - All day is not specified, arbitrarily set to False\")\n+                    #raise UserError(_(\"Need to know if it's an allday or not...\"))\n+\n+            key = \"date\" if allday else \"datetime\"\n+            notkey = \"datetime\" if allday else \"date\"\n+\n+            for fld in ('start', 'stop'):\n+                if values.get('%s_%s' % (fld, key)) or values.get(fld):\n+                    values['%s_%s' % (fld, key)] = values.get('%s_%s' % (fld, key)) or values.get(fld)\n+                    values['%s_%s' % (fld, notkey)] = None\n+                    if fld not in values.keys():\n+                        values[fld] = values['%s_%s' % (fld, key)]\n+\n+            diff = False\n+            if allday and (values.get('stop_date') or values.get('start_date')):\n+                stop_date = values.get('stop_date') or event.stop_date\n+                start_date = values.get('start_date') or event.start_date\n+                if stop_date and start_date:\n+                    diff = fields.Date.from_string(stop_date) - fields.Date.from_string(start_date)\n+            elif values.get('stop_datetime') or values.get('start_datetime'):\n+                stop_datetime = values.get('stop_datetime') or event.stop_datetime\n+                start_datetime = values.get('start_datetime') or event.start_datetime\n+                if stop_datetime and start_datetime:\n+                    diff = fields.Datetime.from_string(stop_datetime) - fields.Datetime.from_string(start_datetime)\n+            if diff:\n+                duration = float(diff.days) * 24 + (float(diff.seconds) / 3600)\n+                values['duration'] = round(duration, 2)\n+\n+    @api.model\n+    def _get_default_partners(self):\n+        ret = [self.env.user.partner_id.id]\n+        active_id = self._context.get('active_id')\n+        if self._context.get('active_model') == 'res.partner' and active_id:\n+            if active_id not in ret:\n+                ret.append(active_id)\n+        return ret\n+\n+    state = fields.Selection([('draft', 'Unconfirmed'), ('open', 'Confirmed')], string='Status', readonly=True, default='draft', track_visibility='onchange')\n+    name = fields.Char('Meeting Subject', required=True, states={'done': [('readonly', True)]})\n+    is_attendee = fields.Boolean(compute='_compute_is_attendee', string='Attendee')\n+    attendee_status = fields.Selection(compute='_compute_attendee_status', string='Attendee Status', selection=CalendarAttendee.STATE_SELECTION)\n+    display_time = fields.Char(compute='_compute_display_time', string='Event Time')\n+    display_start = fields.Char(compute='_compute_display_start', string='Date', store=True)\n+    allday = fields.Boolean('All Day', default=False, states={'done': [('readonly', True)]})\n+    start = fields.Datetime(compute='_compute_start', inverse=lambda *args: None, string='Start', store=True, required=True, help=\"Start date of an event, without time for full days events\")\n+    stop = fields.Datetime(compute='_compute_stop', string='Stop', store=True, required=True, help=\"Stop date of an event, without time for full days events\")\n+    start_date = fields.Date('Start Date', states={'done': [('readonly', True)]}, track_visibility='onchange')\n+    start_datetime = fields.Datetime('Start DateTime', states={'done': [('readonly', True)]}, track_visibility='onchange')\n+    stop_date = fields.Date('End Date', states={'done': [('readonly', True)]}, track_visibility='onchange')\n+    stop_datetime = fields.Datetime('End Datetime', states={'done': [('readonly', True)]}, track_visibility='onchange')  # old date_deadline\n+    duration = fields.Float('Duration', states={'done': [('readonly', True)]})\n+    description = fields.Text('Description', states={'done': [('readonly', True)]})\n+    privacy = fields.Selection([('public', 'Everyone'), ('private', 'Only me'), ('confidential', 'Only internal users')], default='public', oldname='class', states={'done': [('readonly', True)]})\n+    location = fields.Char(help=\"Location of Event\", track_visibility='onchange', states={'done': [('readonly', True)]})\n+    show_as = fields.Selection([('free', 'Free'), ('busy', 'Busy')], 'Show Time as', default='busy', states={'done': [('readonly', True)]})\n+\n+    # RECURRENCE FIELD\n+    rrule = fields.Char(compute='_get_rulestring', inverse='_set_rulestring', store=True, string='Recurrent Rule')\n+    rrule_type = fields.Selection([('daily', 'Day(s)'), ('weekly', 'Week(s)'), ('monthly', 'Month(s)'), ('yearly', 'Year(s)')], 'Recurrency', default=False, states={'done': [('readonly', True)]}, help=\"Let the event automatically repeat at that interval\")\n+    recurrency = fields.Boolean('Recurrent', help=\"Recurrent Meeting\")\n+    recurrent_id = fields.Integer('Recurrent ID')\n+    recurrent_id_date = fields.Datetime('Recurrent ID date')\n+    end_type = fields.Selection([('count', 'Number of repetitions'), ('end_date', 'End date')], 'Recurrence Termination', default='count')\n+    interval = fields.Integer('Repeat Every', default=1, help=\"Repeat every (Days/Week/Month/Year)\")\n+    count = fields.Integer('Repeat', default=1, help=\"Repeat x times\")\n+    mo = fields.Boolean('Mon')\n+    tu = fields.Boolean('Tue')\n+    we = fields.Boolean('Wed')\n+    th = fields.Boolean('Thu')\n+    fr = fields.Boolean('Fri')\n+    sa = fields.Boolean('Sat')\n+    su = fields.Boolean('Sun')\n+    month_by = fields.Selection([('date', 'Date of month'), ('day', 'Day of month')], string='Option', default='date', oldname='select1')\n+    day = fields.Integer('Date of month')\n+    week_list = fields.Selection([('MO', 'Monday'), ('TU', 'Tuesday'), ('WE', 'Wednesday'), ('TH', 'Thursday'), ('FR', 'Friday'), ('SA', 'Saturday'), ('SU', 'Sunday')], 'Weekday')\n+    byday = fields.Selection([('1', 'First'), ('2', 'Second'), ('3', 'Third'), ('4', 'Fourth'), ('5', 'Fifth'), ('-1', 'Last')], 'By day')\n+    final_date = fields.Date('Repeat Until')  # The last event of a recurrence\n+\n+    user_id = fields.Many2one('res.users', 'Responsible', default=lambda self: self.env.uid, states={'done': [('readonly', True)]})\n+    color_partner_id = fields.Many2one(related='user_id.partner_id', string=\"Color index of creator\")  # Color of creator\n+    active = fields.Boolean(default=True, help=\"If the active field is set to false, it will allow you to hide the event alarm information without removing it.\")\n+    categ_ids = fields.Many2many('calendar.event.type', 'meeting_category_rel', 'event_id', 'type_id', 'Tags')\n+    attendee_ids = fields.One2many('calendar.attendee', 'event_id', 'Attendees', ondelete='cascade')\n+    partner_ids = fields.Many2many('res.partner', 'calendar_event_res_partner_rel', string='Attendees', default=_get_default_partners, states={'done': [('readonly', True)]})\n+    alarm_ids = fields.Many2many('calendar.alarm', 'calendar_alarm_calendar_event_rel', string='Reminders', ondelete=\"restrict\", copy=False)\n+\n+    @api.constrains('start_datetime', 'stop_datetime', 'start_date', 'stop_date')\n+    def _check_closing_date(self):\n+        for event in self:\n+            if event.stop < event.start:\n+                return False\n+        return True\n+\n+    @api.onchange('allday')\n+    def onchange_allday(self):\n+        if not ((self.start_date and self.stop_date) or (self.start and self.end)):  # At first intialize, we have not datetime\n+            return\n+\n+        if self.allday:  # from datetime to date\n+            self.start_datetime = self.start_datetime or self.start\n+            if self.start_datetime:\n+                start = fields.Datetime.from_string(self.start_datetime)\n+                self.start_date = fields.Datetime.to_string(start)\n+\n+            self.stop_datetime = self.stop_datetime or self.stop\n+            if self.stop_datetime:\n+                stop = fields.Datetime.from_string(self.stop_datetime)\n+                self.stop_date = fields.Datetime.to_string(stop)\n+        else:  # from date to datetime\n+            tz = pytz.timezone(self.env.user.tz) if self.env.user.tz else pytz.utc\n+\n+            if self.start_date:\n+                start = fields.Datetime.from_string(self.start_date)\n+                startdate = tz.localize(start)  # Add \"+hh:mm\" timezone\n+                startdate = startdate.replace(hour=8)  # Set 8 AM in localtime\n+                startdate = startdate.astimezone(pytz.utc)  # Convert to UTC","path":"addons/calendar/models/calendar.py","position":370,"original_position":370,"commit_id":"e1e7b8b1832bd988fd89fe6377fc8b33d6ead8b0","original_commit_id":"e1e7b8b1832bd988fd89fe6377fc8b33d6ead8b0","user":{"login":"msh-odoo","id":7600161,"avatar_url":"https://avatars.githubusercontent.com/u/7600161?v=3","gravatar_id":"","url":"https://api.github.com/users/msh-odoo","html_url":"https://github.com/msh-odoo","followers_url":"https://api.github.com/users/msh-odoo/followers","following_url":"https://api.github.com/users/msh-odoo/following{/other_user}","gists_url":"https://api.github.com/users/msh-odoo/gists{/gist_id}","starred_url":"https://api.github.com/users/msh-odoo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/msh-odoo/subscriptions","organizations_url":"https://api.github.com/users/msh-odoo/orgs","repos_url":"https://api.github.com/users/msh-odoo/repos","events_url":"https://api.github.com/users/msh-odoo/events{/privacy}","received_events_url":"https://api.github.com/users/msh-odoo/received_events","type":"User","site_admin":false},"body":"``` startdate = tz.localize(start).replace(hour=8).astimezone(pytz.utc) ```","created_at":"2015-12-08T10:38:28Z","updated_at":"2015-12-08T10:38:28Z","html_url":"https://github.com/odoo/odoo/pull/9781#discussion_r46937661","pull_request_url":"https://api.github.com/repos/odoo/odoo/pulls/9781","_links":{"self":{"href":"https://api.github.com/repos/odoo/odoo/pulls/comments/46937661"},"html":{"href":"https://github.com/odoo/odoo/pull/9781#discussion_r46937661"},"pull_request":{"href":"https://api.github.com/repos/odoo/odoo/pulls/9781"}}},"pull_request":{"url":"https://api.github.com/repos/odoo/odoo/pulls/9781","id":51977864,"html_url":"https://github.com/odoo/odoo/pull/9781","diff_url":"https://github.com/odoo/odoo/pull/9781.diff","patch_url":"https://github.com/odoo/odoo/pull/9781.patch","issue_url":"https://api.github.com/repos/odoo/odoo/issues/9781","number":9781,"state":"open","locked":false,"title":"Master calendar newapi mpa","user":{"login":"mpa-odoo","id":10608807,"avatar_url":"https://avatars.githubusercontent.com/u/10608807?v=3","gravatar_id":"","url":"https://api.github.com/users/mpa-odoo","html_url":"https://github.com/mpa-odoo","followers_url":"https://api.github.com/users/mpa-odoo/followers","following_url":"https://api.github.com/users/mpa-odoo/following{/other_user}","gists_url":"https://api.github.com/users/mpa-odoo/gists{/gist_id}","starred_url":"https://api.github.com/users/mpa-odoo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mpa-odoo/subscriptions","organizations_url":"https://api.github.com/users/mpa-odoo/orgs","repos_url":"https://api.github.com/users/mpa-odoo/repos","events_url":"https://api.github.com/users/mpa-odoo/events{/privacy}","received_events_url":"https://api.github.com/users/mpa-odoo/received_events","type":"User","site_admin":false},"body":"<b>Pad:</b> https://pad.odoo.com/p/r.oLHIPZhEsSC6goe4\r\n<b>Task:</b> https://www.odoo.com/web#id=12719&view_type=form&model=project.task&menu_id=3942&action=327","created_at":"2015-11-27T10:05:36Z","updated_at":"2015-12-08T10:38:28Z","closed_at":null,"merged_at":null,"merge_commit_sha":"bdecb3dbf12b88ce61ad91db5d716543d817a043","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/odoo/odoo/pulls/9781/commits","review_comments_url":"https://api.github.com/repos/odoo/odoo/pulls/9781/comments","review_comment_url":"https://api.github.com/repos/odoo/odoo/pulls/comments{/number}","comments_url":"https://api.github.com/repos/odoo/odoo/issues/9781/comments","statuses_url":"https://api.github.com/repos/odoo/odoo/statuses/e1e7b8b1832bd988fd89fe6377fc8b33d6ead8b0","head":{"label":"odoo-dev:master-calendar-newapi-mpa","ref":"master-calendar-newapi-mpa","sha":"e1e7b8b1832bd988fd89fe6377fc8b33d6ead8b0","user":{"login":"odoo-dev","id":7466976,"avatar_url":"https://avatars.githubusercontent.com/u/7466976?v=3","gravatar_id":"","url":"https://api.github.com/users/odoo-dev","html_url":"https://github.com/odoo-dev","followers_url":"https://api.github.com/users/odoo-dev/followers","following_url":"https://api.github.com/users/odoo-dev/following{/other_user}","gists_url":"https://api.github.com/users/odoo-dev/gists{/gist_id}","starred_url":"https://api.github.com/users/odoo-dev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odoo-dev/subscriptions","organizations_url":"https://api.github.com/users/odoo-dev/orgs","repos_url":"https://api.github.com/users/odoo-dev/repos","events_url":"https://api.github.com/users/odoo-dev/events{/privacy}","received_events_url":"https://api.github.com/users/odoo-dev/received_events","type":"Organization","site_admin":false},"repo":{"id":19853934,"name":"odoo","full_name":"odoo-dev/odoo","owner":{"login":"odoo-dev","id":7466976,"avatar_url":"https://avatars.githubusercontent.com/u/7466976?v=3","gravatar_id":"","url":"https://api.github.com/users/odoo-dev","html_url":"https://github.com/odoo-dev","followers_url":"https://api.github.com/users/odoo-dev/followers","following_url":"https://api.github.com/users/odoo-dev/following{/other_user}","gists_url":"https://api.github.com/users/odoo-dev/gists{/gist_id}","starred_url":"https://api.github.com/users/odoo-dev/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odoo-dev/subscriptions","organizations_url":"https://api.github.com/users/odoo-dev/orgs","repos_url":"https://api.github.com/users/odoo-dev/repos","events_url":"https://api.github.com/users/odoo-dev/events{/privacy}","received_events_url":"https://api.github.com/users/odoo-dev/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/odoo-dev/odoo","description":"RD Dev Repository","fork":true,"url":"https://api.github.com/repos/odoo-dev/odoo","forks_url":"https://api.github.com/repos/odoo-dev/odoo/forks","keys_url":"https://api.github.com/repos/odoo-dev/odoo/keys{/key_id}","collaborators_url":"https://api.github.com/repos/odoo-dev/odoo/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/odoo-dev/odoo/teams","hooks_url":"https://api.github.com/repos/odoo-dev/odoo/hooks","issue_events_url":"https://api.github.com/repos/odoo-dev/odoo/issues/events{/number}","events_url":"https://api.github.com/repos/odoo-dev/odoo/events","assignees_url":"https://api.github.com/repos/odoo-dev/odoo/assignees{/user}","branches_url":"https://api.github.com/repos/odoo-dev/odoo/branches{/branch}","tags_url":"https://api.github.com/repos/odoo-dev/odoo/tags","blobs_url":"https://api.github.com/repos/odoo-dev/odoo/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/odoo-dev/odoo/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/odoo-dev/odoo/git/refs{/sha}","trees_url":"https://api.github.com/repos/odoo-dev/odoo/git/trees{/sha}","statuses_url":"https://api.github.com/repos/odoo-dev/odoo/statuses/{sha}","languages_url":"https://api.github.com/repos/odoo-dev/odoo/languages","stargazers_url":"https://api.github.com/repos/odoo-dev/odoo/stargazers","contributors_url":"https://api.github.com/repos/odoo-dev/odoo/contributors","subscribers_url":"https://api.github.com/repos/odoo-dev/odoo/subscribers","subscription_url":"https://api.github.com/repos/odoo-dev/odoo/subscription","commits_url":"https://api.github.com/repos/odoo-dev/odoo/commits{/sha}","git_commits_url":"https://api.github.com/repos/odoo-dev/odoo/git/commits{/sha}","comments_url":"https://api.github.com/repos/odoo-dev/odoo/comments{/number}","issue_comment_url":"https://api.github.com/repos/odoo-dev/odoo/issues/comments{/number}","contents_url":"https://api.github.com/repos/odoo-dev/odoo/contents/{+path}","compare_url":"https://api.github.com/repos/odoo-dev/odoo/compare/{base}...{head}","merges_url":"https://api.github.com/repos/odoo-dev/odoo/merges","archive_url":"https://api.github.com/repos/odoo-dev/odoo/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/odoo-dev/odoo/downloads","issues_url":"https://api.github.com/repos/odoo-dev/odoo/issues{/number}","pulls_url":"https://api.github.com/repos/odoo-dev/odoo/pulls{/number}","milestones_url":"https://api.github.com/repos/odoo-dev/odoo/milestones{/number}","notifications_url":"https://api.github.com/repos/odoo-dev/odoo/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/odoo-dev/odoo/labels{/name}","releases_url":"https://api.github.com/repos/odoo-dev/odoo/releases{/id}","created_at":"2014-05-16T11:16:19Z","updated_at":"2015-12-06T02:47:41Z","pushed_at":"2015-12-08T10:29:37Z","git_url":"git://github.com/odoo-dev/odoo.git","ssh_url":"git@github.com:odoo-dev/odoo.git","clone_url":"https://github.com/odoo-dev/odoo.git","svn_url":"https://github.com/odoo-dev/odoo","homepage":"https://www.odoo.com","size":1153888,"stargazers_count":65,"watchers_count":65,"language":null,"has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":26,"mirror_url":null,"open_issues_count":0,"forks":26,"open_issues":0,"watchers":65,"default_branch":"master"}},"base":{"label":"odoo:master","ref":"master","sha":"ae56601818c238266d6da89f6252e9430d6ea083","user":{"login":"odoo","id":6368483,"avatar_url":"https://avatars.githubusercontent.com/u/6368483?v=3","gravatar_id":"","url":"https://api.github.com/users/odoo","html_url":"https://github.com/odoo","followers_url":"https://api.github.com/users/odoo/followers","following_url":"https://api.github.com/users/odoo/following{/other_user}","gists_url":"https://api.github.com/users/odoo/gists{/gist_id}","starred_url":"https://api.github.com/users/odoo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odoo/subscriptions","organizations_url":"https://api.github.com/users/odoo/orgs","repos_url":"https://api.github.com/users/odoo/repos","events_url":"https://api.github.com/users/odoo/events{/privacy}","received_events_url":"https://api.github.com/users/odoo/received_events","type":"Organization","site_admin":false},"repo":{"id":19745004,"name":"odoo","full_name":"odoo/odoo","owner":{"login":"odoo","id":6368483,"avatar_url":"https://avatars.githubusercontent.com/u/6368483?v=3","gravatar_id":"","url":"https://api.github.com/users/odoo","html_url":"https://github.com/odoo","followers_url":"https://api.github.com/users/odoo/followers","following_url":"https://api.github.com/users/odoo/following{/other_user}","gists_url":"https://api.github.com/users/odoo/gists{/gist_id}","starred_url":"https://api.github.com/users/odoo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/odoo/subscriptions","organizations_url":"https://api.github.com/users/odoo/orgs","repos_url":"https://api.github.com/users/odoo/repos","events_url":"https://api.github.com/users/odoo/events{/privacy}","received_events_url":"https://api.github.com/users/odoo/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/odoo/odoo","description":"Odoo (formerly OpenERP). Open Source Apps To Grow Your Business.","fork":false,"url":"https://api.github.com/repos/odoo/odoo","forks_url":"https://api.github.com/repos/odoo/odoo/forks","keys_url":"https://api.github.com/repos/odoo/odoo/keys{/key_id}","collaborators_url":"https://api.github.com/repos/odoo/odoo/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/odoo/odoo/teams","hooks_url":"https://api.github.com/repos/odoo/odoo/hooks","issue_events_url":"https://api.github.com/repos/odoo/odoo/issues/events{/number}","events_url":"https://api.github.com/repos/odoo/odoo/events","assignees_url":"https://api.github.com/repos/odoo/odoo/assignees{/user}","branches_url":"https://api.github.com/repos/odoo/odoo/branches{/branch}","tags_url":"https://api.github.com/repos/odoo/odoo/tags","blobs_url":"https://api.github.com/repos/odoo/odoo/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/odoo/odoo/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/odoo/odoo/git/refs{/sha}","trees_url":"https://api.github.com/repos/odoo/odoo/git/trees{/sha}","statuses_url":"https://api.github.com/repos/odoo/odoo/statuses/{sha}","languages_url":"https://api.github.com/repos/odoo/odoo/languages","stargazers_url":"https://api.github.com/repos/odoo/odoo/stargazers","contributors_url":"https://api.github.com/repos/odoo/odoo/contributors","subscribers_url":"https://api.github.com/repos/odoo/odoo/subscribers","subscription_url":"https://api.github.com/repos/odoo/odoo/subscription","commits_url":"https://api.github.com/repos/odoo/odoo/commits{/sha}","git_commits_url":"https://api.github.com/repos/odoo/odoo/git/commits{/sha}","comments_url":"https://api.github.com/repos/odoo/odoo/comments{/number}","issue_comment_url":"https://api.github.com/repos/odoo/odoo/issues/comments{/number}","contents_url":"https://api.github.com/repos/odoo/odoo/contents/{+path}","compare_url":"https://api.github.com/repos/odoo/odoo/compare/{base}...{head}","merges_url":"https://api.github.com/repos/odoo/odoo/merges","archive_url":"https://api.github.com/repos/odoo/odoo/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/odoo/odoo/downloads","issues_url":"https://api.github.com/repos/odoo/odoo/issues{/number}","pulls_url":"https://api.github.com/repos/odoo/odoo/pulls{/number}","milestones_url":"https://api.github.com/repos/odoo/odoo/milestones{/number}","notifications_url":"https://api.github.com/repos/odoo/odoo/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/odoo/odoo/labels{/name}","releases_url":"https://api.github.com/repos/odoo/odoo/releases{/id}","created_at":"2014-05-13T15:38:58Z","updated_at":"2015-12-08T10:04:40Z","pushed_at":"2015-12-08T10:35:23Z","git_url":"git://github.com/odoo/odoo.git","ssh_url":"git@github.com:odoo/odoo.git","clone_url":"https://github.com/odoo/odoo.git","svn_url":"https://github.com/odoo/odoo","homepage":"https://www.odoo.com","size":1229167,"stargazers_count":3475,"watchers_count":3475,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":3182,"mirror_url":null,"open_issues_count":2953,"forks":3182,"open_issues":2953,"watchers":3475,"default_branch":"9.0"}},"_links":{"self":{"href":"https://api.github.com/repos/odoo/odoo/pulls/9781"},"html":{"href":"https://github.com/odoo/odoo/pull/9781"},"issue":{"href":"https://api.github.com/repos/odoo/odoo/issues/9781"},"comments":{"href":"https://api.github.com/repos/odoo/odoo/issues/9781/comments"},"review_comments":{"href":"https://api.github.com/repos/odoo/odoo/pulls/9781/comments"},"review_comment":{"href":"https://api.github.com/repos/odoo/odoo/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/odoo/odoo/pulls/9781/commits"},"statuses":{"href":"https://api.github.com/repos/odoo/odoo/statuses/e1e7b8b1832bd988fd89fe6377fc8b33d6ead8b0"}}}}