class Application(BaseModel): _public_fields = [] LEVEL_ALL = 10 LEVEL_3RD_PARTY = 1 id = IntField() name = StringField(max_length=40) key = StringField(max_length=40) level = IntField() def generate_key(self): key_str = '%s:%s:%s' % (str(self.id), self.name, str( datetime.utcnow())) self.key = sha1(key_str).hexdigest() @staticmethod def parse(row): if not row: return None return Application(**row) @staticmethod def findByKey(key): try: cursor = Application.cursor() cursor.execute(FIND_BY_KEY, [key]) return Application.parse(cursor.fetchone()) except Exception, e: Application.report_error(e, cursor) return None
class Movie(Media): """Subclass of Foo. Adds bar and limits publicly shareable fields to only 'bar'. """ _public_fields = ['title', 'year'] year = IntField(min_value=1950, max_value=datetime.datetime.now().year) personal_thoughts = StringField(max_length=255)
class SubDoc(SimpleDoc): """Subclass of `SimpleDoc`. Adds `year` and limits publicly shareable fields to only 'title' and 'year'. """ _public_fields = ['title','year'] year = IntField(min_value=1950, max_value=datetime.datetime.now().year) thoughts = StringField(max_length=255)
class Integration(PyvotalEmbeddedDocument): """Parsed response from api with integration info. Available as :attr:`Project.integrations <pyvotal.projects.Project.integrations>` Available fields: +----------------------------------+----------------------------------------------+ |id |Integer | +----------------------------------+----------------------------------------------+ |type |String | +----------------------------------+----------------------------------------------+ |name |String | +----------------------------------+----------------------------------------------+ |field_name |String | +----------------------------------+----------------------------------------------+ |field_label |String | +----------------------------------+----------------------------------------------+ |active |Boolean | +----------------------------------+----------------------------------------------+ """ id = IntField() type = StringField() name = StringField() field_name = StringField() field_label = StringField() active = PyBooleanField() _tagname = 'integration'
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 Attachment(PyvotalEmbeddedDocument): id = IntField() filename = StringField() description = StringField() uploaded_by = StringField() uploaded_at = PyDateTimeField() utl = StringField() _tagname = 'attachment'
class Movie(Document): """Simple document that has one StringField member """ _public_fields = ["title", "year"] title = StringField(max_length=40, minimized_field_name="t") year = IntField(min_value=1950, max_value=datetime.datetime.now().year, minimized_field_name="y") personal_thoughts = StringField(max_length=255, minimized_field_name="p")
class Interest(BaseModel): _public_fields = [ 'name', 'followers_count', 'conversations_count', 'logged_user_follows' ] _key = 0x7f74cd5c id = IDField(_key) ext_id = OpaqueIdField(_key) name = StringField(max_length=45) followers_count = IntField() conversations_count = IntField() logged_user_follows = BooleanField() @staticmethod def parse(row): if not row: return None return Interest(**row) @staticmethod def parse_all(rows): records = [] for row in rows: records.append(Interest.parse(row)) return records @staticmethod def find(id, user_id=None): try: cursor = Interest.cursor() if user_id: cursor.execute(FIND_WUSER, [user_id, id, id]) else: cursor.execute(FIND, [id]) return Interest.parse(cursor.fetchone()) except Exception, e: Interest.report_error(e, cursor) return None
class ShortLink(Document): """A UserProfile is essentially any publicly available info about the user. Stored in a document separate from the User itself for security. """ link = URLField(max_length=100) short_id = StringField(max_length=10) # 10 is HUGE created_at = MillisecondField() updated_at = MillisecondField() dereferences = IntField(default=0) def __unicode__(self): return u'<shortlink: %s>' % (self.short_id)
class Task(PyvotalDocument): """Parsed response from api with task info. Use :meth:`PTracker.Task <pyvotal.PTracker.Task>` to create instances of this class. Available fields: +----------------------------------------+----------------------------------------+ |id |Integer | +----------------------------------------+----------------------------------------+ |description |String | +----------------------------------------+----------------------------------------+ |position |Integer | +----------------------------------------+----------------------------------------+ |compelete |Boolean | +----------------------------------------+----------------------------------------+ |created_at |datetime | +----------------------------------------+----------------------------------------+ """ description = StringField() position = IntField() complete = PyBooleanField() created_at = PyDateTimeField() _tagname = 'task' def save(self): """Saves changes to existing task back to pivotal. :: from pyvotal import PTracker ptracker = PTracker(token='token') story = ptracker.Story() story.id = story_id story.project_id = project_id task = story.tasks.get(task_id) task.complete = True task.save() """ # FIXME do we need this data = self._to_xml(excludes=['id', 'created_at']) self.client.put('%s/%s' % (self.endpoint, self.id), data)
class Iteration(PyvotalDocument): """Parsed response from api with iteration info. Available fields: +----------------------------------------+----------------------------------------+ |id |Integer | +----------------------------------------+----------------------------------------+ |number |Integer | +----------------------------------------+----------------------------------------+ |start |datetime | +----------------------------------------+----------------------------------------+ |finish |datetime | +----------------------------------------+----------------------------------------+ |team_strength |Float | +----------------------------------------+----------------------------------------+ |stories |list of :class:`~pyvotal.stories.Story` | +----------------------------------------+----------------------------------------+ """ number = IntField() start = PyDateTimeField() finish = PyDateTimeField() team_strength = FloatField() _tagname = 'iteration' def _contribute_from_etree(self, etree): list_value = [] xpath = "stories/story" for tree in etree.findall(xpath): obj = Story() obj.client = self.client obj.project = self.project obj._from_etree(tree) list_value.append(obj) setattr(self, 'stories', list_value)
class Conversation(BaseModel): _public_fields = [ 'created_at', 'location', 'message', 'interest', 'author', 'replies_count', 'distance', 'parent' ] _key = 0x9c12cf6b id = IDField(_key) ext_id = OpaqueIdField(_key) created_at = DateTimeField() location = GeoPointField() message = StringField(max_length=2000) interest = EmbeddedDocumentField(Interest) author = EmbeddedDocumentField(User) replies_count = IntField() score = FloatField() distance = FloatField() #def __new__(cls, *args, **kwargs): # cls.parent = EmbeddedDocumentField(cls) # return object.__new__(cls, *args, **kwargs) def to_sqldict(self): data = BaseModel.to_sqldict(self) data['parent_id'] = self.parent.id if self.parent else None return data def to_dict(self): data = BaseModel.to_dict(self) if self.parent: data['parent'] = self.parent.to_dict() return data @staticmethod def parse(row, **kwargs): if not row: return None if len(kwargs) > 0: row.update(kwargs) obj = Conversation(**row) obj.author = User(id=row.get('user_id'), name=row.get('username'), avatar_url=row.get('avatar_url')) if row.get('lat') and row.get('lon'): obj.location = {'lat': row.get('lat'), 'lon': row.get('lon')} if row.get('interest_id') or row.get('interest_name'): obj.interest = Interest(id=row.get('interest_id'), name=row.get('interest_name')) if row.get('parent_id') or row.get('parent_name'): obj.parent = Conversation(id=row.get('parent_id'), name=row.get('parent_name')) return obj @staticmethod def parse_all(rows): records = [] for row in rows: records.append(Conversation.parse(row)) return records @staticmethod def find(interest_id, id): try: cursor = Conversation.cursor() cursor.execute(FIND, [id, interest_id]) return Conversation.parse(cursor.fetchone()) except Exception, e: Conversation.report_error(e, cursor) return None
class User(BaseModel): _public_fields = [ 'name', 'avatar_url', 'conversations_count', 'interests_count' ] STATUS_ACTIVE = 1 STATUS_PENDING = 2 STATUS_BLOCKED = 3 STATUS_REMOVED = 4 BUCKET_NAME = 'boardhood_profile_images' ALLOWED_IMAGES = set(['png', 'jpg', 'jpeg', 'gif']) id = IntField() name = StringField(max_length=45) email = EmailField() password = StringField() status = IntField() created_at = DateTimeField() updated_at = DateTimeField() avatar_url = URLField() conversations_count = IntField() interests_count = IntField() ip = StringField() timestamp = DateTimeField() def encrypt_password(self): self.password = bcrypt.hashpw(self.password, bcrypt.gensalt()) def to_self_dict(self): data = self.to_dict() data.update({'email': self.email}) return data @staticmethod def parse(row): if not row: return None return User(**row) @staticmethod def parse_all(rows): records = [] for row in rows: records.append(User.parse(row)) return records @staticmethod def create(user): try: cursor = User.cursor() user.encrypt_password() user.status = User.STATUS_ACTIVE user.validate() cursor.execute(CREATE, user.to_sqldict()) User.db.commit() user.id = cursor.fetchone()['id'] return user except ShieldException, e: raise ValidationException(e) except DatabaseError, e: if e.pgcode == '23505': raise UniqueViolationException(e) else: User.report_error(e, cursor) return False
def setUp(self): self.listfield = SortedListField(IntField()) class TestDocument(Document): the_list = self.listfield self.TestDoc = TestDocument self.testdoc = TestDocument()
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 SimpleDoc(Document): title = StringField(max_length=40) num = IntField() class Meta: id_field = ObjectIdField
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()
class SimpleDoc(Document): title = StringField(max_length=40) num = IntField()
class Note(PyvotalEmbeddedDocument): id = IntField() text = StringField() author = StringField() noted_ad = PyDateTimeField() _tagname = 'note'
class Product(EmbeddedDocument): sku = IntField(min_value=1, max_value=9999, required=True) title = StringField(max_length = 30, required=True) description = StringField() price = FloatField(required=True) num_in_stock = IntField()
class Movie(Document): """Simple document that has one StringField member """ title = StringField(max_length=40) year = IntField(min_value=1950, max_value=datetime.datetime.now().year) personal_thoughts = StringField(max_length=255)
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'}