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]))
 
 
+@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')
 @click.option('--text', prompt='Text')
 @click.option('--item_id', prompt='ID of the item to add comment to')
@@ -158,7 +174,7 @@ def shell(ctx):
     import IPython; IPython.embed()
 
 
-for group in [milestone, sprint, item]:
+for group in [milestone, sprint]:
     for command in [new, list]:
         group.add_command(command)
 

+ 1 - 1
pyplanner/converters.py

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

+ 3 - 9
pyplanner/database.py

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

+ 9 - 3
pyplanner/models/__init__.py

@@ -20,7 +20,7 @@ class Base:
     @classmethod
     def to_yaml(cls, representer, node):
         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
@@ -32,14 +32,20 @@ class Base:
         return self.uuid[:8]
 
 
+from .collections import Collection
 from .milestones import Milestone
 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
 
+
 __all__ = [
+    'Collection',
     'Milestone',
     'Sprint',
-    'Item', 'Limitation', 'Constraint', 'Feature', 'Unknown',
+    'Item',
+    'Limitation', 'Constraint', 'Feature', 'Unknown',
+    'ItemType', 'ItemPriority', 'Length',
     '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 sqlalchemy import Column, Integer, String, Text, ForeignKey
 from sqlalchemy.orm import relationship, backref
+from sqlalchemy.ext.hybrid import hybrid_property
 from pydantic import BaseModel
 from enum import Enum
 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
-class Length(Enum):
+class Length(BaseEnum, Enum):
     MINUTES = 'mi'
     SEVERAL_MINUTES = 'mi+'
     HOURS = 'h'
@@ -21,7 +34,7 @@ class Length(Enum):
     SEVERAL_MONTHS = 'mo+'
 
     def __add__(self, other):
-        all_values = list(Length.__members__.values())
+        all_values = list(Length)
         if self == other and '+' not in self.value:
             return Length(f'{self.value}+')
         elif self.value in other.value:
@@ -37,20 +50,35 @@ class Length(Enum):
         raise NotImplementedError()
 
     def __lt__(self, other):
-        all_values = list(Length.__members__.values())
+        all_values = list(Length)
         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):
     name: str
     description: str
-    type: str
+    type: ItemType
     uuid: str
 
-    milestone_id: int
-    sprint_id: int
+    collection_id: int
 
-    priority: str
+    priority: ItemPriority = ItemPriority.SO_SO
     length: Length = Length.DAYS
 
 
@@ -63,42 +91,76 @@ class Item(SQLABase, Base):
     name = Column(String)
     description = Column(Text)
     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__ = {
-        'polymorphic_on': type,
-        'polymorphic_identity': 'item'
+        'polymorphic_on': _type,
+        'polymorphic_identity': ItemType.ITEM.value,
+        'with_polymorphic': '*'
     }
 
 
 class Limitation(Item):
     __mapper_args__ = {
-        'polymorphic_identity': 'limitation'
+        'polymorphic_identity': ItemType.LIMITATION.value
     }
 
 
 class Feature(Item):
     __mapper_args__ = {
-        'polymorphic_identity': 'feature'
+        'polymorphic_identity': ItemType.FEATURE.value
     }
 
 
 class Constraint(Item):
     __mapper_args__ = {
-        'polymorphic_identity': 'constraint'
+        'polymorphic_identity': ItemType.CONSTRAINT.value
     }
 
 
 class Unknown(Item):
     __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 .collections import Collection
 
 
 class SchemaMilestone(BaseModel):
@@ -10,15 +9,18 @@ class SchemaMilestone(BaseModel):
     uuid: str
 
 
-class Milestone(Base, SQLABase):
+class Milestone(Collection):
     _schema = SchemaMilestone
 
     __tablename__ = 'milestones'
-    id = Column(Integer, primary_key=True)
+    id = Column(Integer, ForeignKey('collections.id'), primary_key=True)
 
     name = Column(String)
     description = Column(Text)
-    uuid = Column(String)
+
+    __mapper_args__ = {
+        'polymorphic_identity': 'milestone'
+    }
 
     def __str__(self):
         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.orm import relationship, backref
 from pydantic import BaseModel
+from .collections import Collection
 
 
 class SchemaSprint(BaseModel):
@@ -12,15 +11,18 @@ class SchemaSprint(BaseModel):
     milestone_id: int
 
 
-class Sprint(SQLABase, Base):
+class Sprint(Collection):
     _schema = SchemaSprint
 
     __tablename__ = 'sprints'
-    id = Column(Integer, primary_key=True)
+    id = Column(Integer, ForeignKey('collections.id'), primary_key=True)
 
     name = Column(String)
     description = Column(Text)
-    uuid = Column(String)
 
     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'
+    }