Browse Source

Complete models & add new_item command

theenglishway (time) 6 years ago
parent
commit
81b3d90923

+ 17 - 1
pyplanner/cli.py

@@ -122,6 +122,22 @@ def new(ctx, **data):
         click.echo(Term.failure(e.args[0]))
         click.echo(Term.failure(e.args[0]))
 
 
 
 
+@item.command('new')
+@click.option('--name', prompt='Name')
+@click.option('--description', prompt='Description')
+@click.option('--collection_id', type=int, prompt='Collection ID')
+@click.option('--type', prompt='Type', type=click.Choice([v.value for v in ItemType]))
+@click.pass_context
+def new_item(ctx, **data):
+    model = Item
+    db = ctx.obj['db']
+    try:
+        instance = db.create(model, data)
+        click.echo(Term.success(instance))
+    except ValueError as e:
+        click.echo(Term.failure(e.args[0]))
+
+
 @comment.command('new')
 @comment.command('new')
 @click.option('--text', prompt='Text')
 @click.option('--text', prompt='Text')
 @click.option('--item_id', prompt='ID of the item to add comment to')
 @click.option('--item_id', prompt='ID of the item to add comment to')
@@ -158,7 +174,7 @@ def shell(ctx):
     import IPython; IPython.embed()
     import IPython; IPython.embed()
 
 
 
 
-for group in [milestone, sprint, item]:
+for group in [milestone, sprint]:
     for command in [new, list]:
     for command in [new, list]:
         group.add_command(command)
         group.add_command(command)
 
 

+ 1 - 1
pyplanner/converters.py

@@ -1,4 +1,4 @@
-import ruamel
+import ruamel.yaml
 
 
 
 
 class ConverterYaml:
 class ConverterYaml:

+ 3 - 9
pyplanner/database.py

@@ -2,18 +2,12 @@ from sqlalchemy import create_engine
 from sqlalchemy.orm import sessionmaker
 from sqlalchemy.orm import sessionmaker
 from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy.ext.declarative import declarative_base
 import inspect
 import inspect
-from ruamel import yaml
 import uuid
 import uuid
 
 
 
 
 SQLABase = declarative_base()
 SQLABase = declarative_base()
 
 
 
 
-yaml = yaml.YAML()
-yaml.default_flow_style = False
-yaml.allow_unicode = True
-
-
 class Database:
 class Database:
     def __init__(self, db_url):
     def __init__(self, db_url):
         self.engine = create_engine(db_url)
         self.engine = create_engine(db_url)
@@ -29,14 +23,14 @@ class Database:
         session = self.session_factory()
         session = self.session_factory()
         data.update({'uuid': uuid.uuid4().hex})
         data.update({'uuid': uuid.uuid4().hex})
 
 
-        ok, errors = model.validate(data)
+        ok, obj_or_errors = model.validate(data)
         if ok:
         if ok:
-            m = model(**data)
+            m = model(**obj_or_errors.dict())
             session.add(m)
             session.add(m)
             session.commit()
             session.commit()
             return m
             return m
         else:
         else:
-            raise ValueError(errors)
+            raise ValueError(obj_or_errors)
 
 
     def edit(self, model, data):
     def edit(self, model, data):
         session = self.session_factory()
         session = self.session_factory()

+ 9 - 3
pyplanner/models/__init__.py

@@ -20,7 +20,7 @@ class Base:
     @classmethod
     @classmethod
     def to_yaml(cls, representer, node):
     def to_yaml(cls, representer, node):
         return representer.represent_data(
         return representer.represent_data(
-            {k: v for k, v in node.__dict__.items() if not k.startswith('_')}
+            {k: v for k, v in node.__dict__.items() if k != '_sa_instance_state'}
         )
         )
 
 
     @classmethod
     @classmethod
@@ -32,14 +32,20 @@ class Base:
         return self.uuid[:8]
         return self.uuid[:8]
 
 
 
 
+from .collections import Collection
 from .milestones import Milestone
 from .milestones import Milestone
 from .sprints import Sprint
 from .sprints import Sprint
-from .items import Item, Limitation, Constraint, Feature, Unknown
+from .items import (Item, Limitation, Constraint, Feature, Unknown,
+                    ItemType, ItemPriority, Length)
 from .comments import Comment
 from .comments import Comment
 
 
+
 __all__ = [
 __all__ = [
+    'Collection',
     'Milestone',
     'Milestone',
     'Sprint',
     'Sprint',
-    'Item', 'Limitation', 'Constraint', 'Feature', 'Unknown',
+    'Item',
+    'Limitation', 'Constraint', 'Feature', 'Unknown',
+    'ItemType', 'ItemPriority', 'Length',
     'Comment'
     'Comment'
 ]
 ]

+ 16 - 0
pyplanner/models/collections.py

@@ -0,0 +1,16 @@
+from pyplanner.database import SQLABase
+from pyplanner.models import Base
+from sqlalchemy import Column, Integer, String
+
+
+class Collection(Base, SQLABase):
+    __tablename__ = 'collections'
+    id = Column(Integer, primary_key=True)
+    type = Column(String)
+    uuid = Column(String)
+
+    __mapper_args__ = {
+        'polymorphic_on': type,
+        'polymorphic_identity': 'collection',
+        'with_polymorphic': '*'
+    }

+ 82 - 20
pyplanner/models/items.py

@@ -2,13 +2,26 @@ from pyplanner.database import SQLABase
 from pyplanner.models import Base
 from pyplanner.models import Base
 from sqlalchemy import Column, Integer, String, Text, ForeignKey
 from sqlalchemy import Column, Integer, String, Text, ForeignKey
 from sqlalchemy.orm import relationship, backref
 from sqlalchemy.orm import relationship, backref
+from sqlalchemy.ext.hybrid import hybrid_property
 from pydantic import BaseModel
 from pydantic import BaseModel
 from enum import Enum
 from enum import Enum
 from functools import total_ordering
 from functools import total_ordering
 
 
 
 
+class BaseEnum:
+    @classmethod
+    def to_yaml(cls, representer, node):
+        return representer.represent_scalar(
+            node.value
+        )
+
+    @classmethod
+    def from_yaml(cls, constructor, node):
+        return cls(node)
+
+
 @total_ordering
 @total_ordering
-class Length(Enum):
+class Length(BaseEnum, Enum):
     MINUTES = 'mi'
     MINUTES = 'mi'
     SEVERAL_MINUTES = 'mi+'
     SEVERAL_MINUTES = 'mi+'
     HOURS = 'h'
     HOURS = 'h'
@@ -21,7 +34,7 @@ class Length(Enum):
     SEVERAL_MONTHS = 'mo+'
     SEVERAL_MONTHS = 'mo+'
 
 
     def __add__(self, other):
     def __add__(self, other):
-        all_values = list(Length.__members__.values())
+        all_values = list(Length)
         if self == other and '+' not in self.value:
         if self == other and '+' not in self.value:
             return Length(f'{self.value}+')
             return Length(f'{self.value}+')
         elif self.value in other.value:
         elif self.value in other.value:
@@ -37,20 +50,35 @@ class Length(Enum):
         raise NotImplementedError()
         raise NotImplementedError()
 
 
     def __lt__(self, other):
     def __lt__(self, other):
-        all_values = list(Length.__members__.values())
+        all_values = list(Length)
         return all_values.index(self) < all_values.index(other)
         return all_values.index(self) < all_values.index(other)
 
 
 
 
+class ItemType(BaseEnum, Enum):
+    ITEM = 'none'
+    LIMITATION = 'lim'
+    FEATURE = 'feat'
+    CONSTRAINT = 'cons'
+    UNKNOWN = 'unk'
+
+
+class ItemPriority(BaseEnum, Enum):
+    VERY_NOT_IMPORTANT = '--'
+    NOT_IMPORTANT = '-'
+    SO_SO = '='
+    IMPORTANT = '+'
+    VERY_IMPORTANT = '++'
+
+
 class SchemaItem(BaseModel):
 class SchemaItem(BaseModel):
     name: str
     name: str
     description: str
     description: str
-    type: str
+    type: ItemType
     uuid: str
     uuid: str
 
 
-    milestone_id: int
-    sprint_id: int
+    collection_id: int
 
 
-    priority: str
+    priority: ItemPriority = ItemPriority.SO_SO
     length: Length = Length.DAYS
     length: Length = Length.DAYS
 
 
 
 
@@ -63,42 +91,76 @@ class Item(SQLABase, Base):
     name = Column(String)
     name = Column(String)
     description = Column(Text)
     description = Column(Text)
     uuid = Column(String)
     uuid = Column(String)
-    type = Column(String)
+    _type = Column(String)
+
+    collection_id = Column(Integer, ForeignKey('collections.id'))
+    collection = relationship('Collection', backref=backref('items'))
+
+    _priority = Column(String)
+    _length = Column(String)
+
+    @hybrid_property
+    def type(self):
+        return ItemType(self._type)
+
+    @type.setter
+    def type(self, value):
+        self._type = value.value
+
+    @type.expression
+    def type(cls):
+        return cls._type
+
+    @hybrid_property
+    def length(self):
+        return Length(self._length)
+
+    @length.setter
+    def length(self, value):
+        self._length = value.value
+
+    @length.expression
+    def length(cls):
+        return cls._length
 
 
-    milestone_id = Column(Integer, ForeignKey('milestones.id'))
-    milestone = relationship('Milestone', backref=backref('items'))
+    @hybrid_property
+    def priority(self):
+        return ItemPriority(self._priority)
 
 
-    sprint_id = Column(Integer, ForeignKey('sprints.id'))
-    sprint = relationship('Sprint', backref=backref('items'))
+    @priority.setter
+    def priority(self, value):
+        self._priority = value.value
 
 
-    priority = Column(String)
-    length = Column(String)
+    @priority.expression
+    def priority(cls):
+        return cls._priority
 
 
     __mapper_args__ = {
     __mapper_args__ = {
-        'polymorphic_on': type,
-        'polymorphic_identity': 'item'
+        'polymorphic_on': _type,
+        'polymorphic_identity': ItemType.ITEM.value,
+        'with_polymorphic': '*'
     }
     }
 
 
 
 
 class Limitation(Item):
 class Limitation(Item):
     __mapper_args__ = {
     __mapper_args__ = {
-        'polymorphic_identity': 'limitation'
+        'polymorphic_identity': ItemType.LIMITATION.value
     }
     }
 
 
 
 
 class Feature(Item):
 class Feature(Item):
     __mapper_args__ = {
     __mapper_args__ = {
-        'polymorphic_identity': 'feature'
+        'polymorphic_identity': ItemType.FEATURE.value
     }
     }
 
 
 
 
 class Constraint(Item):
 class Constraint(Item):
     __mapper_args__ = {
     __mapper_args__ = {
-        'polymorphic_identity': 'constraint'
+        'polymorphic_identity': ItemType.CONSTRAINT.value
     }
     }
 
 
 
 
 class Unknown(Item):
 class Unknown(Item):
     __mapper_args__ = {
     __mapper_args__ = {
-        'polymorphic_identity': 'unknown'
+        'polymorphic_identity': ItemType.UNKNOWN.value
     }
     }

+ 8 - 6
pyplanner/models/milestones.py

@@ -1,7 +1,6 @@
-from pyplanner.database import SQLABase
-from pyplanner.models import Base
-from sqlalchemy import Column, Integer, String, Text
+from sqlalchemy import Column, Integer, String, Text, ForeignKey
 from pydantic import BaseModel
 from pydantic import BaseModel
+from .collections import Collection
 
 
 
 
 class SchemaMilestone(BaseModel):
 class SchemaMilestone(BaseModel):
@@ -10,15 +9,18 @@ class SchemaMilestone(BaseModel):
     uuid: str
     uuid: str
 
 
 
 
-class Milestone(Base, SQLABase):
+class Milestone(Collection):
     _schema = SchemaMilestone
     _schema = SchemaMilestone
 
 
     __tablename__ = 'milestones'
     __tablename__ = 'milestones'
-    id = Column(Integer, primary_key=True)
+    id = Column(Integer, ForeignKey('collections.id'), primary_key=True)
 
 
     name = Column(String)
     name = Column(String)
     description = Column(Text)
     description = Column(Text)
-    uuid = Column(String)
+
+    __mapper_args__ = {
+        'polymorphic_identity': 'milestone'
+    }
 
 
     def __str__(self):
     def __str__(self):
         return f"{self.__class__.__qualname__} '{self.name}'"
         return f"{self.__class__.__qualname__} '{self.name}'"

+ 8 - 6
pyplanner/models/sprints.py

@@ -1,8 +1,7 @@
-from pyplanner.database import SQLABase
-from pyplanner.models import Base
 from sqlalchemy import Column, Integer, String, Text, ForeignKey
 from sqlalchemy import Column, Integer, String, Text, ForeignKey
 from sqlalchemy.orm import relationship, backref
 from sqlalchemy.orm import relationship, backref
 from pydantic import BaseModel
 from pydantic import BaseModel
+from .collections import Collection
 
 
 
 
 class SchemaSprint(BaseModel):
 class SchemaSprint(BaseModel):
@@ -12,15 +11,18 @@ class SchemaSprint(BaseModel):
     milestone_id: int
     milestone_id: int
 
 
 
 
-class Sprint(SQLABase, Base):
+class Sprint(Collection):
     _schema = SchemaSprint
     _schema = SchemaSprint
 
 
     __tablename__ = 'sprints'
     __tablename__ = 'sprints'
-    id = Column(Integer, primary_key=True)
+    id = Column(Integer, ForeignKey('collections.id'), primary_key=True)
 
 
     name = Column(String)
     name = Column(String)
     description = Column(Text)
     description = Column(Text)
-    uuid = Column(String)
 
 
     milestone_id = Column(Integer, ForeignKey('milestones.id'))
     milestone_id = Column(Integer, ForeignKey('milestones.id'))
-    milestone = relationship('Milestone', backref=backref('sprints'))
+    milestone = relationship('Milestone', backref=backref('sprints'), foreign_keys=[milestone_id])
+
+    __mapper_args__ = {
+        'polymorphic_identity': 'sprint'
+    }