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)
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()
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)
class TestDocument(Document): the_doc = ListField(self.embedded_field)
def setUp(self): self.listfield = ListField(IntField()) class TestDocument(Document): the_list = self.listfield self.TestDoc = TestDocument self.testdoc = TestDocument()
class TestDocument(Document): the_doc = ListField([self.embedded_field, self.second_embedded_field])
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])
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())
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()
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'}
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()