class WorkspacedEntity(base.TogglEntity): """ Abstract entity which has linked Workspace """ workspace = fields.MappingField(Workspace, 'wid', write=False, default=lambda config: config.default_workspace) """
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
def test_basic(self): class A: pass with pytest.raises(TypeError): fields.MappingField(A, 'a')
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)
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)
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
class EvaluateConditionsEntityMapping(EvaluateConditionsEntity): mapping = fields.MappingField(RandomEntity, 'rid')
class EntityWithDefaultMapping(Entity): default = fields.MappingField(RandomEntity, 'eid', default=a)
class EntityWithRequiredMapping(Entity): mapping = fields.MappingField(Entity, 'eid', required=True)
class EntityWithMapping(Entity): mapping = fields.MappingField(RandomEntity, 'eid')
class ExtendedMetaTestEntityWithConflicts(MetaTestEntity): some_other_mapped_field = fields.MappingField(RandomEntity, 'mapping_field')
class ExtendedMetaTestEntity(MetaTestEntity): another_string = fields.StringField() another_mapped = fields.MappingField(RandomEntity, 'another_mapping_field')
class MetaTestEntity(metaclass=base.TogglEntityMeta): id = fields.IntegerField() string = fields.StringField() boolean = fields.BooleanField() mapped = fields.MappingField(RandomEntity, 'mapping_field')