class TaskList(Document):
    """A `TaskList` associated a creation date and updated_date with a list of
    `Action` instances.
    """
    actions = ListField(EmbeddedDocumentField(Action))
    created_date = DateTimeField(default=datetime.datetime.now)
    updated_date = DateTimeField(default=datetime.datetime.now)
    num_completed = IntField(default=0)
Exemple #2
0
class BlogPost(Document):
    _private_fields=['personal_thoughts']
    _public_fields=['author', 'content', 'comments']
    title = StringField()    
    content = StringField()
    author = EmbeddedDocumentField(Author)
    comments = ListField(EmbeddedDocumentField(Comment))
    deleted = BooleanField()   
Exemple #3
0
class ListItem(Document, OwnedModelMixin, StreamedModelMixin):
    """Bare minimum to have the concept of streamed item.
    """
    # status fields
    liked = BooleanField(default=False)
    deleted = BooleanField(default=False)
    archived = BooleanField(default=False)

    url = URLField(required=True)
    title = StringField(required=True)
    tags = ListField(StringField())

    class Meta:
        id_field = ObjectIdField

    _private_fields = [
        'owner_id',
    ]

    def __unicode__(self):
        return u'%s' % (self.url)
Exemple #4
0
 class TestDocument(Document):
     the_doc = ListField(self.embedded_field)
Exemple #5
0
 def setUp(self):
     self.listfield = ListField(IntField())
     class TestDocument(Document):
         the_list = self.listfield
     self.TestDoc = TestDocument
     self.testdoc = TestDocument()
Exemple #6
0
 class TestDocument(Document):
     the_doc = ListField([self.embedded_field, self.second_embedded_field])
Exemple #7
0
class TestSetGetSingleScalarData(unittest.TestCase):
    def setUp(self):
        self.listfield = ListField(IntField())
        class TestDocument(Document):
            the_list = self.listfield
        self.TestDoc = TestDocument
        self.testdoc = TestDocument()

    def test_good_value_for_python(self):
        self.testdoc.the_list = [2]
        self.assertEqual(self.testdoc.the_list, [2])

    def test_single_bad_value_for_python(self):
        self.testdoc.the_list = 2
        self.assertEqual(self.testdoc.the_list, 2) #since no validation happens, nothing should yell at us

    def test_collection_good_values_for_python(self):
        self.testdoc.the_list = [2,2,2,2,2,2]
        self.assertEqual(self.testdoc.the_list, [2,2,2,2,2,2])

    def test_collection_bad_values_for_python(self):
        expected = self.testdoc.the_list = ["2","2","2","2","2","2"]
        actual = self.testdoc.the_list
        self.assertEqual(actual, expected)  #since no validation happens, nothing should yell at us

    def test_good_value_for_json(self):
        expected = self.testdoc.the_list = [2]
        actual = self.listfield.for_json(self.testdoc.the_list)
        self.assertEqual(actual, expected)

    def test_good_values_for_json(self):
        expected = self.testdoc.the_list = [2,2,2,2,2,2]
        actual = self.listfield.for_json(self.testdoc.the_list)
        self.assertEqual(actual, expected)

    def test_good_values_into_json(self):
        self.testdoc.the_list = [2,2,2,2,2,2]
        actual = self.TestDoc.make_json_ownersafe(self.testdoc)
        expected = json.dumps({"the_list":[2,2,2,2,2,2]})
        self.assertEqual(actual, expected)

    def test_good_value_into_json(self):
        self.testdoc.the_list = [2]
        actual = self.TestDoc.make_json_ownersafe(self.testdoc)
        expected = json.dumps({"the_list":[2]})
        self.assertEqual(actual, expected)

    def test_good_value_validates(self):
        self.testdoc.the_list = [2,2,2,2,2,2]
        self.testdoc.validate()

    def test_coerceible_value_passes_validation(self):
        self.testdoc.the_list = ["2","2","2","2","2","2"]
        self.testdoc.validate()

    def test_uncoerceible_value_passes_validation(self):
        self.testdoc.the_list = ["2","2","2","2","horse","2"]
        self.assertRaises(ShieldException, self.testdoc.validate)        
        
    @unittest.expectedFailure
    def test_validation_converts_value(self):
        self.testdoc.the_list = ["2","2","2","2","2","2"]
        self.testdoc.validate()
        self.assertEqual(self.testdoc.the_list, [2,2,2,2,2,2])
Exemple #8
0
class Post(BlogDocument):
    """A post or a page"""
    title = StringField(default='')
    # Formatted for display.
    body = StringField(default='')
    # Input from MarsEdit or migrate_from_wordpress.
    original = StringField(default='')
    # Plain text.
    plain = StringField(default='')
    # Plain-text excerpt.
    summary = StringField(default='')
    author = StringField(default='')
    type = StringField(choices=('post', 'page'), default='post')
    status = StringField(choices=('publish', 'draft', 'redirect'),
                         default='publish')
    meta_description = StringField(default='')
    tags = SortedListField(StringField())
    categories = SortedListField(EmbeddedDocumentField(EmbeddedCategory))
    slug = StringField(default='')
    guest_access_tokens = ListField(EmbeddedDocumentField(GuestAccessToken))
    wordpress_id = IntField()  # legacy id from WordPress
    pub_date = DateTimeField()
    mod = DateTimeField()
    # Post was moved, this is its new slug.
    redirect = StringField(default=None)

    def __init__(self, *args, **kwargs):
        super(Post, self).__init__(*args, **kwargs)
        if not self.mod.tzinfo:
            self.mod = utc_tz.localize(self.mod)

    @classmethod
    def from_metaweblog(cls, struct, post_type='post', is_edit=False):
        """Receive metaWeblog RPC struct and initialize a Post.
           Used both by migrate_from_wordpress and when receiving a new or
           edited post from MarsEdit.
        """
        title = struct.get('title', '')

        meta_description = struct.get('mt_excerpt', '')
        if len(meta_description) > 155:
            raise ValueError("Description is %d chars, max 155" %
                             len(meta_description))

        if 'mt_keywords' in struct:
            tags = [
                tag.strip() for tag in struct['mt_keywords'].split(',')
                if tag.strip()
            ]
        else:
            tags = None

        slug = (slugify.slugify(struct['wp_slug'])
                if struct.get('wp_slug') else slugify.slugify(title))

        description = struct.get('description', '')
        status = (struct.get('post_status') or struct.get('page_status')
                  or 'publish')

        if 'date_modified_gmt' in struct:
            tup = struct['date_modified_gmt'].timetuple()
            mod = utc_tz.localize(datetime.datetime(*tup[0:6]))
        else:
            mod = datetime.datetime.utcnow()

        body = markup.markup(description)

        rv = cls(
            title=title,
            # Format for display
            body=body,
            plain=plain.plain(body),
            summary=summarize.summarize(body, 200),
            original=description,
            meta_description=meta_description,
            tags=tags,
            slug=slug,
            type=post_type,
            status=status,
            wordpress_id=struct.get('postid'),
            mod=mod)

        if not is_edit and 'date_created_gmt' in struct:
            # TODO: can fail if two posts created in same second, add random
            #   suffix to ObjectId
            date_created = datetime.datetime.strptime(
                struct['date_created_gmt'].value, "%Y%m%dT%H:%M:%S")
            rv.id = ObjectId.from_datetime(date_created)

        return rv

    def to_metaweblog(self, application):
        # We're kind of throwing fieldnames at the wall and seeing what sticks,
        # MarsEdit expects different names in the responses to different API
        # calls.

        if self.status == 'publish':
            url = absolute(application.reverse_url('post', self.slug))
        else:
            url = absolute(application.reverse_url('draft', self.slug))

        rv = {
            'title':
            self.title,
            # Note we're returning the original, not the display version
            'description':
            self.original,
            'link':
            url,
            'permaLink':
            url,
            'categories':
            [cat.to_metaweblog(application) for cat in self['categories']],
            'mt_keywords':
            ','.join(self['tags']),
            'dateCreated':
            self.local_date_created(application),
            'date_created_gmt':
            self.date_created,
            'postid':
            str(self.id),
            'id':
            str(self.id),
            'status':
            self.status,
            'wp_slug':
            self.slug,
            'mt_excerpt':
            self.meta_description,
        }

        if self.type == 'post':
            rv['post_id'] = str(self.id)
            rv['post_status'] = self.status
        elif self.type == 'page':
            rv['page_id'] = str(self.id)
            rv['page_status'] = self.status

        return rv

    def to_python(self):
        dct = super(Post, self).to_python()

        # Avoid bug where metaWeblog_editPost() sets categories to []
        if not self.categories:
            dct.pop('categories', None)

        # TODO: for other models, too?
        if 'id' in dct:
            dct['_id'] = dct.pop('id')
        return dct

    @property
    def date_created(self):
        if self.pub_date:
            return utc_tz.localize(self.pub_date)
        else:
            return super(Post, self).date_created

    def local_date_created(self, application):
        dc = self.date_created
        tz = application.settings['tz']
        return tz.normalize(dc.astimezone(tz))

    def local_short_date(self, application):
        dc = self.local_date_created(application)
        return '%s/%s/%s' % (dc.month, dc.day, dc.year)

    def local_long_date(self, application):
        dc = self.local_date_created(application)
        return '%s %s, %s' % (dc.strftime('%b'), dc.day, dc.year)

    def local_time_of_day(self, application):
        dc = self.local_date_created(application)
        return '%d:%02d %s' % (dc.hour % 12, dc.minute, dc.strftime('%p'))

    @property
    def last_modified(self):
        return max(self.date_created, self.mod)

    @property
    def display_summary(self):
        return self.meta_description if self.meta_description else self.summary

    def has_guest_access_token(self, token):
        """Is the given ObjectId a valid access token?"""
        assert isinstance(token, ObjectId)
        for token_object in self.guest_access_tokens:
            if token_object.token == token:
                return True

        return False
 class Other(EmbeddedDocument):
     info = ListField(fields.StringField())
Exemple #10
0
class Action(EmbeddedDocument):
    """An `Action` associates an action name with a list of tags.
    """
    value = StringField(required=True, max_length=256)
    tags = ListField(StringField())
class Customer(User):
    date_made = DateTimeField(required=True)
    first_name = StringField(max_length=20, required=True)
    last_name = StringField(max_length=30, required=True)
    orders = ListField(EmbeddedDocumentField(Order))
class Order(EmbeddedDocument):
    date_made = DateTimeField(required=True)
    date_changed = DateTimeField()
    line_items = ListField(EmbeddedDocumentField(Product))
    total = FloatField()
Exemple #13
0
class Project(PyvotalDocument):
    """Parsed response from api with project info.
Use :meth:`PTracker.Project <pyvotal.PTracker.Project>` to create instances of this class.

Available fields:

+----------------------------------+----------------------------------------------+
|id                                |Integer                                       |
+----------------------------------+----------------------------------------------+
|name                              |String                                        |
+----------------------------------+----------------------------------------------+
|iteraton_length                   |Integer                                       |
+----------------------------------+----------------------------------------------+
|week_start_day                    |String                                        |
+----------------------------------+----------------------------------------------+
|pont_scale                        |String                                        |
+----------------------------------+----------------------------------------------+
|account                           |String                                        |
+----------------------------------+----------------------------------------------+
|first_iteration_start_time        |datetime                                      |
+----------------------------------+----------------------------------------------+
|current_iteration_number          |Integer                                       |
+----------------------------------+----------------------------------------------+
|enable_tasks                      |Boolean                                       |
+----------------------------------+----------------------------------------------+
|velocity_scheme                   |String                                        |
+----------------------------------+----------------------------------------------+
|current_velocity                  |Integer                                       |
+----------------------------------+----------------------------------------------+
|initial_velocity                  |Integer                                       |
+----------------------------------+----------------------------------------------+
|number_of_done_iterations_to_show |Integer                                       |
+----------------------------------+----------------------------------------------+
|labels                            |String                                        |
+----------------------------------+----------------------------------------------+
|allow_attachments                 |Boolean                                       |
+----------------------------------+----------------------------------------------+
|public                            |Boolean                                       |
+----------------------------------+----------------------------------------------+
|use_https                         |Boolean                                       |
+----------------------------------+----------------------------------------------+
|bugs_and_chores_are_estimatable   |Boolean                                       |
+----------------------------------+----------------------------------------------+
|commit_mode                       |Boolean                                       |
+----------------------------------+----------------------------------------------+
|last_activity_at                  |datetime                                      |
+----------------------------------+----------------------------------------------+
|integrations                      |list of :class:`~pyvotal.projects.Integration`|
+----------------------------------+----------------------------------------------+

Note: you should check field values for ``None`` as some of them may be missing.

"""
    name = StringField()
    iteration_length = IntField()
    week_start_day = StringField()
    point_scale = StringField()
    account = StringField()
    first_iteration_start_time = PyDateTimeField()
    current_iteration_number = IntField()
    enable_tasks = PyBooleanField()
    velocity_scheme = StringField()
    current_velocity = IntField()
    initial_velocity = IntField()
    number_of_done_iterations_to_show = IntField()
    labels = StringField()
    allow_attachments = PyBooleanField()
    public = PyBooleanField()
    use_https = PyBooleanField()
    bugs_and_chores_are_estimatable = PyBooleanField()
    commit_mode = PyBooleanField()
    last_activity_at = PyDateTimeField()

    integrations = ListField(EmbeddedDocumentField(Integration))

    xml_exclude = ['integrations']

    _tagname = 'project'

    @property
    def memberships(self):
        """:class:`~pyvotal.memberships.MembershipManager` to manipulate project`s memberships."""
        if self.id is None:
            raise PyvotalException("Project does not have id")
        if not getattr(self, '_memberships', None):
            self._memberships = MembershipManager(self.client, self.id)

        return self._memberships

    @property
    def iterations(self):
        """:class:`~pyvotal.iterations.IterationManager` to manipulate project`s iterations."""
        if self.id is None:
            raise PyvotalException("Project does not have id")
        if not getattr(self, '_iterations', None):
            self._iterations = IterationManager(self.client, self.id)

        return self._iterations

    @property
    def stories(self):
        """:class:`~pyvotal.stories.StoryManager` to manipulate project`s stories."""
        if self.id is None:
            raise PyvotalException("Project does not have id")
        if not getattr(self, '_stories', None):
            self._stories = StoryManager(self.client, self.id, self)

        return self._stories

    def _contribute_to_xml(self, etree):
        if getattr(self, "no_owner", False):
            elem = SubElement(etree, "no_owner")
            elem.text = str(True).lower()
            elem.attrib = {'type': 'boolean'}
Exemple #14
0
class Story(PyvotalDocument):
    """Parsed response from api with story info. Use :meth:`PTracker.Story <pyvotal.PTracker.Story>` to create instances of this class.

Available fields:

+----------------------------------------+----------------------------------------+
|id                                      |Integer                                 |
+----------------------------------------+----------------------------------------+
|project_id                              |Integer                                 |
+----------------------------------------+----------------------------------------+
|story_type                              |String                                  |
+----------------------------------------+----------------------------------------+
|url                                     |String                                  |
+----------------------------------------+----------------------------------------+
|estimate                                |Integer                                 |
+----------------------------------------+----------------------------------------+
|current_state                           |String                                  |
+----------------------------------------+----------------------------------------+
|description                             |String                                  |
+----------------------------------------+----------------------------------------+
|name                                    |String                                  |
+----------------------------------------+----------------------------------------+
|requested_by                            |String                                  |
+----------------------------------------+----------------------------------------+
|owned_by                                |String                                  |
+----------------------------------------+----------------------------------------+
|created_at                              |datetime                                |
+----------------------------------------+----------------------------------------+
|accepted_at                             |datetime                                |
+----------------------------------------+----------------------------------------+
|labels                                  |String                                  |
+----------------------------------------+----------------------------------------+
|notes                                   |list of :class:`Note`                   |
+----------------------------------------+----------------------------------------+
|attachments                             |list of :class:`Attachment`             |
+----------------------------------------+----------------------------------------+

Story could also have more fields depending on existing integrations in project.
To get them use :attr:`Project.integrations`

    """
    project_id = IntField()
    story_type = StringField()
    url = StringField()
    estimate = IntField()
    current_state = StringField()
    description = StringField()
    name = StringField()
    requested_by = StringField()
    owned_by = StringField()
    created_at = PyDateTimeField()
    accepted_at = PyDateTimeField()
    labels = StringField()

    notes = ListField(EmbeddedDocumentField(Note))

    attachments = ListField(EmbeddedDocumentField(Attachment))

    xml_exclude = ['attachments', 'notes']

    _tagname = 'story'

    @property
    def tasks(self):
        """:class:`~pyvotal.tasks.TaskManager` to manipulate story`s tasks."""
        if self.id is None:
            raise PyvotalException("Story does not have id")
        if not getattr(self, '_tasks', None):
            self._tasks = TaskManager(self.client, self.project_id, self.id)

        return self._tasks

    def add_attachment(self, name, fobj):
        """Adds attachment to story.

        :param name: Attachment file name.
        :param fobj: File-like object to upload.

        ::

          from pyvotal import PTracker

          ptracker = PTracker(token='token')

          project = ptracker.projects.get(1231)
          story = projects.stories.get(1232)

          story.add_attachment("hosts", open("/etc/hosts"))"""

        url = 'projects/%s/stories/%s/attachments' % (self.project_id, self.id)
        self.client.post(url, None, files={'Filedata': (name, fobj)})

    def add_note(self, text):
        """Adds note to story.

        :param text: Text to be added to story as note.
        :return: Created :class:`~pyvotal.stories.Note`.

        ::

          from pyvotal import PTracker

          ptracker = PTracker(token='token')

          project = ptracker.projects.get(1231)
          story = projects.stories.get(1232)

          note = story.add_note("Usefull info")
          print note.id, note.noted_at """

        url = 'projects/%s/stories/%s/notes/' % (self.project_id, self.id)
        data = "<note><text>%s</text></note>" % text
        etree = self.client.post(url, data)
        obj = Note()
        obj._from_etree(etree)
        return obj

    def save(self):
        """Saves changes to existing story back to pivotal.

::

  from pyvotal import PTracker

  ptracker = PTracker(token='token')
  project = ptracker.projects.get(1231)

  story = projects.stories.get(1232)
  story.estimate = 3
  story.save()"""

        url = 'projects/%s/stories/%s' % (self.project_id, self.id)
        data = self._to_xml(excludes=['id', 'url'])
        self.client.put(url, data)
        # FIXME return new story

    def move(self, move, target_id):
        url = 'projects/%s/stories/%s/moves' % (self.project_id, self.id)
        kwargs = dict()
        kwargs['move[move]'] = move
        kwargs['move[target]'] = target_id
        data = self.client.post(url, "", params=kwargs)

        obj = Story()
        obj._from_etree(data)
        obj.client = self.client
        return obj

    def move_after(self, story):
        """
        Moves story after given.

        :param story: Story id or :class:`~pyvotal.stories.Story`.
        :return: Updated  :class:`~pyvotal.stories.Story`.

        ::

          from pyvotal import PTracker

          ptracker = PTracker(token='token')
          project = ptracker.projects.get(1231)

          story = projects.stories.get(1232)
          story.move_after(1233)"""

        if isinstance(story, Story):
            story_id = story.id
        else:
            story_id = story
        return self.move('after', story_id)

    def move_before(self, story):
        """
        Moves story before given.

        :param story: Story id or :class:`~pyvotal.stories.Story`.
        :return: Updated  :class:`~pyvotal.stories.Story`.

        ::

          from pyvotal import PTracker

          ptracker = PTracker(token='token')
          project = ptracker.projects.get(1231)

          story = projects.stories.get(1232)
          story.move_before(another_story)"""

        if isinstance(story, Story):
            story_id = story.id
        else:
            story_id = story
        return self.move('before', story_id)

    @property
    def project(self):
        return self._project

    @project.setter
    def project(self, value):
        self._project = value
        if value is not None:
            for integration in self._project.integrations:
                self._fields[integration.field_name] = StringField()