コード例 #1
0
ファイル: models.py プロジェクト: mundo03/toggl-cli
class Client(WorkspacedEntity):
    """
    Client entity
    """

    name = fields.StringField(required=True)
    """
    Name of client (Required)
    """

    notes = fields.StringField()
    """
コード例 #2
0
    def test_make_fields(self):
        fields_set = {
            'id': fields.IntegerField(),
            'str': fields.StringField(default='asd'),
            'req_str': fields.StringField(required=True),
            'something_random': 'asdf'
        }

        result = base.TogglEntityMeta._make_fields(fields_set, [RandomEntity])

        # Four because there is one Field taken from RandomEntity and 'something_random' is ignored
        assert len(result) == 4
        assert result['id'].name == 'id'
コード例 #3
0
    def test_make_signature(self):
        fields_set = {
            'id': fields.IntegerField(),
            'str': fields.StringField(default='asd'),
            'req_str': fields.StringField(required=True),
        }

        self.set_fields_names(fields_set)

        sig = base.TogglEntityMeta._make_signature(fields_set)
        sig_params = sig.parameters

        assert len(sig_params) == 2
        assert sig_params['str'].default == 'asd'
        assert 'id' not in sig_params
コード例 #4
0
ファイル: test_fields.py プロジェクト: tys-hiroshi/toggl-cli
    def test_get_default(self):
        obj = Entity()
        obj.__dict__ = {}

        field = fields.StringField(default='asd')
        field.name = 'field'
        assert field.__get__(obj, None) == 'asd'
コード例 #5
0
ファイル: test_fields.py プロジェクト: tys-hiroshi/toggl-cli
    def test_get_callable_default(self):
        obj = Entity()
        obj.__dict__ = {}

        field = fields.StringField(default=lambda _: '123')
        field.name = 'field'
        assert field.__get__(obj, None) == '123'
コード例 #6
0
ファイル: models.py プロジェクト: mundo03/toggl-cli
class Task(PremiumEntity):
    """
    Task entity.

    This entity is available only for Premium workspaces.
    """

    name = fields.StringField(required=True)
    """
    Name of task
    """

    project = fields.MappingField(Project, 'pid', required=True)
    """
    Project to which the Task is linked to.
    """

    user = fields.MappingField(User, 'uid')
    """
    User to which the Task is assigned to.
    """

    estimated_seconds = fields.IntegerField()
    """
    Estimated duration of task in seconds.
    """

    active = fields.BooleanField(default=True)
    """
    Whether the task is done or not.
    """

    tracked_seconds = fields.IntegerField(write=False)
    """
コード例 #7
0
ファイル: test_fields.py プロジェクト: tys-hiroshi/toggl-cli
    def test_set_required(self):
        obj = Entity()

        field = fields.StringField(required=True)
        field.name = 'field'

        with pytest.raises(TypeError):
            field.__set__(obj, None)
コード例 #8
0
ファイル: test_fields.py プロジェクト: tys-hiroshi/toggl-cli
    def test_set_no_write(self):
        obj = Entity()

        field = fields.StringField(write=False)
        field.name = 'field'

        with pytest.raises(exceptions.TogglNotAllowedException):
            field.__set__(obj, 'asd')
コード例 #9
0
ファイル: models.py プロジェクト: mundo03/toggl-cli
class Tag(WorkspacedEntity):
    """
    Tag entity
    """

    _can_get_detail = False

    name = fields.StringField(required=True)
    """
コード例 #10
0
ファイル: test_fields.py プロジェクト: tys-hiroshi/toggl-cli
    def test_get_no_read(self):
        obj = Entity()

        field = fields.StringField(read=False)
        field.name = 'field'

        # Before anything is set to the field, it is not allowed to read from it.
        with pytest.raises(exceptions.TogglNotAllowedException):
            field.__get__(obj, None)

        # After setting something to it, it is allowed to read from it.
        field.__set__(obj, 'asd')
        assert field.__get__(obj, None) == 'asd'
コード例 #11
0
ファイル: test_fields.py プロジェクト: tys-hiroshi/toggl-cli
    def test_get(self):
        obj = Entity()
        obj.__dict__ = {'field': 123}

        field = fields.StringField()
        with pytest.raises(RuntimeError):
            field.__get__(obj, None)

        field.name = 'field'
        assert field.__get__(obj, None) == 123

        field.name = 'non-existing=field'
        with pytest.raises(AttributeError):
            field.__get__(obj, None)
コード例 #12
0
ファイル: test_fields.py プロジェクト: tys-hiroshi/toggl-cli
    def test_set(self):
        obj = Entity()

        field = fields.StringField()
        with pytest.raises(RuntimeError):
            field.__set__(obj, 'asd')

        field.name = 'field'
        field.__set__(obj, 'asd')
        assert obj.__dict__['field'] == 'asd'
        assert obj.__change_dict__['field'] == 'asd'

        field.__set__(obj, None)
        assert obj.__dict__['field'] is None
コード例 #13
0
    def test_make_mapped_fields(self):
        mapping_field_instance = fields.MappingField(RandomEntity, 'mapped_field')

        fields_set = {
            'id': fields.IntegerField(),
            'str': fields.StringField(),
            'mapping': mapping_field_instance,
            'something_random': 'asdf'
        }

        result = base.TogglEntityMeta._make_mapped_fields(fields_set)

        # Four because there is one Field taken from RandomEntity and 'something_random' is ignored
        assert len(result) == 1
        assert result['mapped_field'] is mapping_field_instance
コード例 #14
0
ファイル: test_fields.py プロジェクト: tys-hiroshi/toggl-cli
    def test_set_admin(self):
        class WorkspaceMock:
            admin = True
            name = 'WorkspaceMock'

        class WorkspacedEntityMock(models.WorkspacedEntity):
            workspace = WorkspaceMock

        obj = WorkspacedEntityMock()

        field = fields.StringField(admin_only=True)
        field.name = 'field'

        field.__set__(obj, 'asd')
        assert obj.__dict__['field'] == 'asd'

        WorkspaceMock.admin = False
        with pytest.raises(exceptions.TogglNotAllowedException):
            field.__set__(obj, 'asd')
コード例 #15
0
ファイル: test_fields.py プロジェクト: tys-hiroshi/toggl-cli
    def test_premium(self):
        class WorkspaceMock:
            premium = False
            name = 'WorkspaceMock'

        class WorkspacedEntityMock(models.WorkspacedEntity):
            workspace = WorkspaceMock
            premium_field = fields.StringField(premium=True)

        with pytest.raises(exceptions.TogglPremiumException):
            obj = WorkspacedEntityMock(premium_field='something')
            obj.save()

        obj = WorkspacedEntityMock()

        field = fields.StringField(premium=True)
        field.name = 'field'

        with pytest.raises(exceptions.TogglPremiumException):
            field.__set__(obj, 'asd')

        WorkspaceMock.premium = True
        field.__set__(obj, 'asd')
        assert obj.__dict__['field'] == 'asd'
コード例 #16
0
ファイル: test_fields.py プロジェクト: tys-hiroshi/toggl-cli
 class WorkspacedEntityMock(models.WorkspacedEntity):
     workspace = WorkspaceMock
     premium_field = fields.StringField(premium=True)
コード例 #17
0
class ExtendedMetaTestEntity(MetaTestEntity):
    another_string = fields.StringField()
    another_mapped = fields.MappingField(RandomEntity, 'another_mapping_field')
コード例 #18
0
ファイル: models.py プロジェクト: mundo03/toggl-cli
class Project(WorkspacedEntity):
    """
    Project entity
    """

    name = fields.StringField(required=True)
    """
    Name of the project. (Required)
    """

    client = fields.MappingField(Client, 'cid')
    """
    Client associated to the project.
    """

    active = fields.BooleanField(default=True)
    """
    Whether the project is archived or not. (Default: True)
    """

    is_private = fields.BooleanField(default=True)
    """
    Whether project is accessible for only project users or for all workspace users. (Default: True)
    """

    billable = fields.BooleanField(premium=True)
    """
    Whether the project is billable or not.

    (Available only for Premium workspaces)
    """

    auto_estimates = fields.BooleanField(default=False, premium=True)
    """
    Whether the estimated hours are automatically calculated based on task estimations or manually
    fixed based on the value of 'estimated_hours'.

    (Available only for Premium workspaces)
    """

    estimated_hours = fields.IntegerField(premium=True)
    """
    If auto_estimates is true then the sum of task estimations is returned, otherwise user inserted hours.

    (Available only for Premium workspaces)
    """

    color = fields.IntegerField()
    """
    Id of the color selected for the project
    """

    hex_color = fields.StringField()
    """
    Hex code of the color selected for the project
    """

    rate = fields.FloatField(premium=True)
    """
    Hourly rate of the project.

    (Available only for Premium workspaces)
    """
    def add_user(
        self,
        user,
        manager=False,
        rate=None
    ):  # type: (User, bool, typing.Optional[float]) -> ProjectUser
        """
        Add new user to a project.

        :param user: User to be added
        :param manager: Specifies if the user should have manager's rights
        :param rate: Rate for billing
        :return: ProjectUser instance.
        """
        project_user = ProjectUser(project=self,
                                   user=user,
                                   workspace=self.workspace,
                                   manager=manager,
                                   rate=rate)
        project_user.save()

        return project_user
コード例 #19
0
class Entity(base.TogglEntity):
    string = fields.StringField()
    integer = fields.IntegerField()
    boolean = fields.BooleanField()
    datetime = fields.DateTimeField()
コード例 #20
0
class EntityWithRequired(Entity):
    required = fields.StringField(required=True)
コード例 #21
0
ファイル: models.py プロジェクト: mundo03/toggl-cli
class TimeEntry(WorkspacedEntity):
    description = fields.StringField()
    """
    Description of the entry.
    """

    project = fields.MappingField(Project, 'pid')
    """
    Project to which the Time entry is linked to.
    """

    task = fields.MappingField(Task, 'tid', premium=True)
    """
    Task to which the Time entry is linked to.

    (Available only for Premium workspaces)
    """

    billable = fields.BooleanField(default=False, premium=True)
    """
    If available to be billed. (Default: False)

    (Available only for Premium workspaces)
    """

    start = TimeEntryDateTimeField(required=True)
    """
    DateTime of start of the time entry. (Required)
    """

    stop = TimeEntryDateTimeField()
    """
    DateTime of end of the time entry.
    """

    duration = fields.PropertyField(get_duration,
                                    set_duration,
                                    formatter=format_duration)
    """
    Dynamic field of entry's duration in seconds.

    If the time entry is currently running, the duration attribute contains a negative value,
    denoting the start of the time entry in seconds since epoch (Jan 1 1970). The correct duration can be
    calculated as current_time + duration, where current_time is the current time in seconds since epoch.
    """

    created_with = fields.StringField(required=True,
                                      default='TogglCLI',
                                      read=False)
    """
    Information who created the time entry.
    """

    tags = fields.SetField()
    """
    Set of tags associated with the time entry.
    """

    objects = TimeEntrySet()

    def __init__(self, start, stop=None, duration=None, **kwargs):
        if stop is None and duration is None:
            raise ValueError(
                'You can create only finished time entries through this way! '
                'You must supply either \'stop\' or \'duration\' parameter!')

        super().__init__(start=start, stop=stop, duration=duration, **kwargs)

    @classmethod
    def get_url(cls):
        return 'time_entries'

    def to_dict(self, serialized=False, changes_only=False):
        # Enforcing serialize duration when start or stop changes
        if changes_only and (self.__change_dict__.get('start')
                             or self.__change_dict__.get('stop')):
            self.__change_dict__['duration'] = None

        return super().to_dict(serialized=serialized,
                               changes_only=changes_only)

    @classmethod
    def start_and_save(
        cls,
        start=None,
        config=None,
        **kwargs
    ):  # type: (pendulum.DateTime, utils.Config, **typing.Any) -> TimeEntry
        """
        Creates a new running entry.

        If there is another running time entry in the time of calling this method, then the running entry is stopped.
        This is handled by Toggl's backend.

        :param start: The DateTime object representing start of the new TimeEntry. If None than current time is used.
        :param config:
        :param kwargs: Other parameters for creating the new TimeEntry
        :return: New running TimeEntry
        """
        config = config or utils.Config.factory()

        if start is None:
            start = pendulum.now(config.timezone)

        if 'stop' in kwargs or 'duration' in kwargs:
            raise RuntimeError(
                'With start_and_save() method you can not create finished entries!'
            )

        instance = cls.__new__(cls)
        instance.__change_dict__ = {}
        instance.is_running = True
        instance._config = config
        instance.start = start

        for key, value in kwargs.items():
            setattr(instance, key, value)

        instance.save()

        return instance

    def stop_and_save(self=None, stop=None):
        """
        Stops running the entry. It has to be running entry.

        :param stop: DateTime which should be set as stop time. If None, then current time is used.
        :return: Self
        """
        if self is None:
            # noinspection PyMethodFirstArgAssignment
            self = TimeEntry.objects.current()
            if self is None:
                raise exceptions.TogglValidationException(
                    'There is no running entry to be stoped!')

        if not self.is_running:
            raise exceptions.TogglValidationException(
                'You can\'t stop not running entry!')

        config = self._config or utils.Config.factory()

        if stop is None:
            stop = pendulum.now(config.timezone)

        self.stop = stop
        self.is_running = False
        self.save(config=config)

        return self

    def continue_and_save(self, start=None):
        """
        Creates new time entry with same description as the self entry and starts running it.

        :param start: The DateTime object representing start of the new TimeEntry. If None than current time is used.
        :return: The new TimeEntry.
        """
        if self.is_running:
            logger.warning(
                'Trying to continue time entry {} which is already running!'.
                format(self))

        config = self._config or utils.Config.factory()

        if start is None:
            start = pendulum.now(config.timezone)

        new_entry = copy(self)
        new_entry.start = start
        new_entry.stop = None
        new_entry.is_running = True

        new_entry.save(config=config)

        return new_entry

    def __str__(self):
        return '{} (#{})'.format(getattr(self, 'description', ''), self.id)
コード例 #22
0
class EntityWithDefault(Entity):
    default = fields.StringField(default='asd')
    callable_default = fields.StringField(default=lambda _: 'aaa')
コード例 #23
0
class MetaTestEntity(metaclass=base.TogglEntityMeta):
    id = fields.IntegerField()
    string = fields.StringField()
    boolean = fields.BooleanField()
    mapped = fields.MappingField(RandomEntity, 'mapping_field')
コード例 #24
0
ファイル: models.py プロジェクト: mundo03/toggl-cli
class User(WorkspacedEntity):
    """
    User entity.
    """

    _can_create = False
    _can_update = False
    _can_delete = False
    _can_get_detail = False

    api_token = fields.StringField()
    """
    API token to use for API calls.

    (Returned only for User.objects.current_user() call.)
    """

    send_timer_notifications = fields.BooleanField()

    default_workspace = fields.MappingField(Workspace,
                                            'default_wid')  # type: Workspace
    """
    Default workspace for calls that does not specify Workspace.

    (Returned only for User.objects.current_user() call.)
    """

    email = fields.EmailField()
    """
    Email address of user.
    """

    fullname = fields.StringField()
    """
    Full name of the user.
    """

    beginning_of_week = fields.ChoiceField({
        '0': 'Sunday',
        '1': 'Monday',
        '2': 'Tuesday',
        '3': 'Wednesday',
        '4': 'Thursday',
        '5': 'Friday',
        '6': 'Saturday'
    })
    """
    Defines which day is the first day of week for the user.
    """

    language = fields.StringField()
    """
    Stores language used for the user.
    """

    image_url = fields.StringField()
    """
    URL of the profile image of the user.
    """

    timezone = fields.StringField()
    """
    Timezone which is used to convert the times into.

    May differ from one used in this tool, see toggl.utils.Config().
    """

    # TODO: Add possibility to use this value in Config.time_format
    timeofday_format = fields.ChoiceField({
        'H:mm': '24-hour',
        'h:mm A': '12-hour'
    })
    """
    Format of time used to display time.
    """

    # TODO: Add possibility to use this value in Config.datetime_format
    date_format = fields.ChoiceField([
        "YYYY-MM-DD", "DD.MM.YYYY", "DD-MM-YYYY", "MM/DD/YYYY", "DD/MM/YYYY",
        "MM-DD-YYYY"
    ])
    """
    Format of date used to display dates.
    """

    objects = UserSet()

    @classmethod
    def signup(
            cls,
            email,
            password,
            timezone=None,
            created_with='TogglCLI',
            config=None):  # type: (str, str, str, str, utils.Config) -> User
        """
        Creates brand new user. After creation confirmation email is sent to him.

        :param email: Valid email of the new user.
        :param password: Password of the new user.
        :param timezone: Timezone to be associated with the user. If empty, than timezone from config is used.
        :param created_with: Name of application that created the user.
        :param config:
        :return:
        """
        if config is None:
            config = utils.Config.factory()

        if timezone is None:
            timezone = config.timezone

        if not validate_email(email):
            raise exceptions.TogglValidationException(
                'Supplied email \'{}\' is not valid email!'.format(email))

        user_json = json.dumps({
            'user': {
                'email': email,
                'password': password,
                'timezone': timezone,
                'created_with': created_with
            }
        })
        data = utils.toggl("/signups", "post", user_json, config=config)
        return cls.deserialize(config=config, **data['data'])

    def is_admin(self, workspace):
        wid = workspace.id if isinstance(workspace, Workspace) else workspace

        workspace_user = WorkspaceUser.objects.get(wid=wid, uid=self.id)
        return workspace_user.admin

    def __str__(self):
        return '{} (#{})'.format(self.fullname, self.id)
コード例 #25
0
ファイル: models.py プロジェクト: mundo03/toggl-cli
class Workspace(base.TogglEntity):
    _can_create = False
    _can_delete = False

    name = fields.StringField(required=True)
    """
    Name of the workspace
    """

    premium = fields.BooleanField()
    """
    If it's a pro workspace or not. Shows if someone is paying for the workspace or not
    """

    admin = fields.BooleanField()
    """
    Shows whether currently requesting user has admin access to the workspace
    """

    only_admins_may_create_projects = fields.BooleanField()
    """
    Whether only the admins can create projects or everybody
    """

    only_admins_see_billable_rates = fields.BooleanField()
    """
    Whether only the admins can see billable rates or everybody
    """

    rounding = fields.IntegerField()
    """
    Type of rounding:

    * round down: -1
    * nearest: 0
    * round up: 1
    """

    rounding_minutes = fields.IntegerField()
    """
    Round up to nearest minute
    """

    default_hourly_rate = fields.FloatField()
    """
    Default hourly rate for workspace, won't be shown to non-admins
    if the only_admins_see_billable_rates flag is set to true
    """

    default_currency = fields.StringField()
    """
    Default currency for workspace
    """

    # As TogglEntityMeta is by default adding WorkspaceTogglSet to TogglEntity,
    # but we want vanilla TogglSet so defining it here explicitly.
    objects = base.TogglSet()

    def invite(self, *emails):  # type: (str) -> None
        """
        Invites users defined by email addresses. The users does not have to have account in Toggl, in that case after
        accepting the invitation, they will go through process of creating the account in the Toggl web.

        :param emails: List of emails to invite.
        :return: None
        """
        for email in emails:
            if not validate_email(email):
                raise exceptions.TogglValidationException(
                    'Supplied email \'{}\' is not valid email!'.format(email))

        emails_json = json.dumps({'emails': emails})
        data = utils.toggl("/workspaces/{}/invite".format(self.id),
                           "post",
                           emails_json,
                           config=self._config)

        if 'notifications' in data and data['notifications']:
            raise exceptions.TogglException(data['notifications'])
コード例 #26
0
ファイル: test_fields.py プロジェクト: tys-hiroshi/toggl-cli
class Entity(base.TogglEntity):
    string = fields.StringField()
    integer = fields.IntegerField()
    boolean = fields.BooleanField()
    float = fields.FloatField()
コード例 #27
0
class RandomEntity(base.TogglEntity):
    some_field = fields.StringField()
コード例 #28
0
class EvaluateConditionsEntity(base.TogglEntity):
    string = fields.StringField()
    integer = fields.IntegerField()
    boolean = fields.BooleanField()
    set = fields.SetField()