Exemple #1
0
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
Exemple #2
0
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)
Exemple #3
0
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)
Exemple #4
0
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)
Exemple #6
0
class Attachment(PyvotalEmbeddedDocument):
    id = IntField()
    filename = StringField()
    description = StringField()
    uploaded_by = StringField()
    uploaded_at = PyDateTimeField()
    utl = StringField()
    _tagname = 'attachment'
Exemple #7
0
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")
Exemple #8
0
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
Exemple #9
0
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)
Exemple #10
0
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)
Exemple #11
0
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)
Exemple #12
0
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
Exemple #13
0
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
Exemple #14
0
 def setUp(self):
     self.listfield = SortedListField(IntField())
     class TestDocument(Document):
         the_list = self.listfield
     self.TestDoc = TestDocument
     self.testdoc = TestDocument()
Exemple #15
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
Exemple #16
0
class SimpleDoc(Document):
    title = StringField(max_length=40)
    num = IntField()

    class Meta:
        id_field = ObjectIdField
Exemple #17
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()
Exemple #18
0
class SimpleDoc(Document):
    title = StringField(max_length=40)
    num = IntField()
Exemple #19
0
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()
Exemple #21
0
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)
Exemple #22
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'}