Skip to content

Commit

Permalink
Implementing batch append, #943 (#967)
Browse files Browse the repository at this point in the history
* Added test for appending batch and model for batch

* Added controller fo batch

* Added new test case for append batch and created controller for this

* Implementing batch append, #943

* Added commit for mockup of append batch test

* A wrong indent
  • Loading branch information
farzaneka authored and pylover committed Jul 1, 2019
1 parent 0225b4f commit 6616772
Show file tree
Hide file tree
Showing 10 changed files with 309 additions and 5 deletions.
42 changes: 42 additions & 0 deletions dolphin/controllers/batch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from nanohttp import json, context, HTTPNotFound, int_or_notfound
from restfulpy.authorization import authorize
from restfulpy.controllers import ModelRestController
from restfulpy.orm import DBSession, commit

from ..exceptions import StatusIssueIdIsNull, StatusInvalidIssueIdType, \
StatusIssueIdNotInForm, StatusIssueNotFound
from ..models import Batch, Issue


class BatchController(ModelRestController):
__model__ = Batch

@authorize
@json
@Batch.expose
@Batch.validate(fields=dict(
issueIds=dict(
required=StatusIssueIdNotInForm,
type_=(int, StatusInvalidIssueIdType),
not_none=StatusIssueIdIsNull,
)
))
@commit
def append(self, id_):
id_ = int_or_notfound(id_)
batch = DBSession.query(Batch).filter(Batch.id == id_).one_or_none()
if batch is None:
raise HTTPNotFound('Batch with id: {id_} was not found')

issue_id = context.form['issueIds']
issue = DBSession.query(Issue) \
.filter(Issue.id == issue_id) \
.one_or_none()

if issue is None:
raise StatusIssueNotFound(issue_id)

batch.issues.append(issue)

return batch

6 changes: 5 additions & 1 deletion dolphin/controllers/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
from ..exceptions import StatusChatRoomNotFound, \
StatusRoomMemberAlreadyExist, StatusRoomMemberNotFound, \
StatusManagerNotFound, StatusSecondaryManagerNotFound
from ..models import Project, Member, Subscription, Workflow, Group, Release
from ..models import Project, Member, Subscription, Workflow, Group, Release, \
Batch
from ..validators import project_validator, update_project_validator
from .files import FileController
from .issues import IssueController
Expand Down Expand Up @@ -222,6 +223,9 @@ def create(self):
)
raise

batch = Batch(title='00')
project.batches.append(batch)

DBSession.add(project)
return project

Expand Down
2 changes: 2 additions & 0 deletions dolphin/controllers/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from .items import ItemController
from .phase_summary import PhaseSummaryController
from .resource_summary import ResourceSummaryController
from .batch import BatchController


here = abspath(dirname(__file__))
Expand Down Expand Up @@ -59,6 +60,7 @@ class Apiv1(RestController, JsonPatchControllerMixin):
items = ItemController()
phasessummaries = PhaseSummaryController()
resourcessummaries = ResourceSummaryController()
batches = BatchController()

@json
def version(self):
Expand Down
9 changes: 8 additions & 1 deletion dolphin/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,11 @@ class StatusLaunchDateMustGreaterThanCutoffDate(HTTPKnownStatus):


class StatusIssueNotFound(HTTPKnownStatus):
status = '605 Issue Not Found'
def __init__(self, issue_id=None):
# This + between strings are decided by PyLover.
# DO NOT DO THAT ANYWHERE
self.status = f'605 Issue Not Found' + \
(f': {issue_id}' if issue_id is not None else '')


class StatusMemberNotFound(HTTPKnownStatus):
Expand Down Expand Up @@ -518,3 +522,6 @@ class StatusInvalidDateFormat(HTTPKnownStatus):
class StatusDateNotInForm(HTTPKnownStatus):
status = '931 Date Not In Form'

class StatusIssueIdNotInForm(HTTPKnownStatus):
status = '723 Issue Id Not In Form'

2 changes: 1 addition & 1 deletion dolphin/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@
from .phase_summary import AbstractPhaseSummaryView
from .resource_summary import AbstractResourceSummaryView
from .issue_phase import IssuePhase

from .batch import Batch
72 changes: 72 additions & 0 deletions dolphin/models/batch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from restfulpy.orm import Field, DeclarativeBase, relationship
from restfulpy.orm.metadata import MetadataField
from sqlalchemy import Integer, ForeignKey, String, Unicode, UniqueConstraint


class Batch(DeclarativeBase):

__tablename__ = 'batch'

id = Field(
Integer,
primary_key=True,
readonly=True,
not_none=True,
required=False,
label='ID',
minimum=1,
example=1,
protected=False,
)
title = Field(
Unicode(50),
min_length=2,
label='Batch title',
watermark='Lorem Ipsum',
not_none=True,
required=True,
python_type=str,
protected=False,
example='Lorem Ipsum'
)
project_id = Field(
Integer,
ForeignKey('project.id'),
not_none=True,
readonly=True
)
issues = relationship(
'Issue',
back_populates='batches',
protected=True
)
projects = relationship(
'Project',
back_populates='batches',
protected=True
)

__table_args__ = (
UniqueConstraint(
title,
project_id,
name='uix_title_project_id'
),
)

def to_dict(self):
batch_dict = super().to_dict()
batch_dict['issueIds'] = [i.id for i in self.issues]
return batch_dict

@classmethod
def iter_metadata_fields(cls):
yield from super().iter_metadata_fields()
yield MetadataField(
name='issue_ids',
key='issueIds',
label='Issues IDs',
required=False,
readonly=True
)

11 changes: 11 additions & 0 deletions dolphin/models/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,17 @@ class Issue(OrderingMixin, FilteringMixin, PaginationMixin, ModifiedByMixin,
example='lorem ipsum',
)
attachments = relationship('Attachment', lazy='selectin')
batch_id = Field(
Integer,
ForeignKey('batch.id'),
readonly=True,
nullable=True
)
batches = relationship(
'Batch',
back_populates='issues',
protected=False
)
tags = relationship(
'Tag',
secondary='issue_tag',
Expand Down
7 changes: 5 additions & 2 deletions dolphin/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ class Project(ModifiedByMixin, OrderingMixin, FilteringMixin, PaginationMixin,
Integer,
ForeignKey('release.id'),
python_type=int,
nullable=False,
watermark='Choose a release',
label='Release',
not_none=True,
Expand Down Expand Up @@ -110,7 +109,11 @@ class Project(ModifiedByMixin, OrderingMixin, FilteringMixin, PaginationMixin,
default='queued',
example='Lorem Ipsum'
)

batches = relationship(
'Batch',
back_populates='projects',
protected=True
)
workflow = relationship(
'Workflow',
back_populates='projects',
Expand Down
124 changes: 124 additions & 0 deletions dolphin/tests/test_batch_append.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
from auditor.context import Context as AuditLogContext
from bddrest import status, response, when, given
from nanohttp.contexts import Context
from nanohttp import context

from dolphin.models import Workflow, Group, Release, Member, Batch, Issue, \
Project
from dolphin.tests.helpers import LocalApplicationTestCase, oauth_mockup_server


class TestBatch(LocalApplicationTestCase):

@classmethod
@AuditLogContext(dict())
def mockup(cls):
session = cls.create_session()

cls.member1 = Member(
title='First Member',
email='[email protected]',
access_token='access token',
phone=123456789,
reference_id=2
)

workflow = Workflow(title='Default')
group = Group(title='default')

release1 = Release(
title='My first release',
description='A decription for my first release',
cutoff='2030-2-20',
launch_date='2030-2-20',
manager=cls.member1,
room_id=0,
group=group,
)

cls.project1 = Project(
release=release1,
workflow=workflow,
group=group,
manager=cls.member1,
title='My first project',
description='A decription for my project',
room_id=1001
)
session.add(cls.project1)
session.commit()

cls.batch1 = Batch(title='01')
cls.project1.batches.append(cls.batch1)

with Context(dict()):
context.identity = cls.member1

cls.issue1 = Issue(
title='First issue',
description='This is description of first issue',
kind='feature',
days=1,
room_id=2
)
cls.project1.issues.append(cls.issue1)
session.commit()

def test_append(self):
session = self.create_session()
self.login('[email protected]')

with oauth_mockup_server(), self.given(
'Appending a batch',
f'/apiv1/batches/id: {self.batch1.id}',
'APPEND',
json=dict(
issueIds=self.issue1.id
)
):
assert status == 200
assert response.json['id'] is not None
assert response.json['title'] == self.batch1.title
assert response.json['projectId'] == self.project1.id
assert self.issue1.id in response.json['issueIds']
assert len(response.json['issueIds']) == 1

when(
'Trying to pass without issue id',
json=given - 'issueIds'
)
assert status == '723 Issue Id Not In Form'

when(
'Trying to pass with invalid issue id type',
json=given | dict(issueIds='a')
)
assert status == '722 Invalid Issue Id Type'

when(
'Trying to pass with none issue id',
json=given | dict(issueIds=None)
)
assert status == '775 Issue Id Is Null'

when(
'Issue is not found',
json=given | dict(issueIds=0)
)
assert status == '605 Issue Not Found: 0'

when(
'Inended batch with integer type not found',
url_parameters=dict(id=0)
)
assert status == 404

when(
'Inended batch with string type not found',
url_parameters=dict(id='Alaphabet')
)
assert status == 404

when('Request is not authorized', authorization=None)
assert status == 401

39 changes: 39 additions & 0 deletions dolphin/tests/test_batch_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from bddrest.authoring import status, response

from dolphin.tests.helpers import LocalApplicationTestCase


class TestBatch(LocalApplicationTestCase):

def test_metadata(self):
with self.given(
'Test metadata verb',
'/apiv1/batches',
'METADATA'
):
assert status == 200

fields = response.json['fields']

assert fields['id']['primaryKey'] is not None
assert fields['id']['readonly'] is not None
assert fields['id']['notNone'] is not None
assert fields['id']['required'] is not None
assert fields['id']['label'] is not None
assert fields['id']['minimum'] is not None
assert fields['id']['example'] is not None
assert fields['id']['protected'] is not None

assert fields['title']['minLength'] is not None
assert fields['title']['label'] is not None
assert fields['title']['watermark'] is not None
assert fields['title']['example'] is not None
assert fields['title']['notNone'] is not None
assert fields['title']['required'] is not None
assert fields['title']['protected'] is not None
assert fields['title']['type'] is not None

assert fields['projectId']['key'] is not None
assert fields['projectId']['notNone'] is not None
assert fields['projectId']['readonly'] is not None

0 comments on commit 6616772

Please sign in to comment.