Skip to content

Commit

Permalink
[301] teams endpoints (fixes#301) (#311)
Browse files Browse the repository at this point in the history
* 300 Team DB model created

* 301 teams endponits added

* 301 linting fixes

* 301 team unit tests minor fixes
  • Loading branch information
stanislawK authored Oct 21, 2020
1 parent c6a5941 commit 409423c
Show file tree
Hide file tree
Showing 9 changed files with 443 additions and 4 deletions.
4 changes: 4 additions & 0 deletions backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
HacknightParticipants,
)
from backend.resources.participant import ParticipantDetails, ParticipantsList
from backend.resources.team import TeamDetails, TeamList, TeamMembers


def create_app():
Expand Down Expand Up @@ -58,3 +59,6 @@ def check_if_token_in_blacklist(decrypted_token):
api.add_resource(SendMessage, "/api/send-email/")
api.add_resource(UserLogin, "/api/auth/login/")
api.add_resource(UserLogout, "/api/auth/logout/")
api.add_resource(TeamList, "/api/teams/")
api.add_resource(TeamDetails, "/api/teams/<int:id>/")
api.add_resource(TeamMembers, "/api/teams/<int:id>/members/")
14 changes: 13 additions & 1 deletion backend/commands/populate_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
from tqdm import tqdm

from backend.extensions import db
from backend.factories import HacknightFactory, ParticipantFactory, UserFactory
from backend.factories import (
HacknightFactory,
ParticipantFactory,
TeamFactory,
UserFactory,
)
from backend.models import Participant


Expand Down Expand Up @@ -33,6 +38,13 @@ def populate_database():
)
db.session.commit()

click.echo("Creating 5 teams")
for _ in tqdm(range(5)):
TeamFactory.create(
members=random.sample(all_participants, random.randint(1, 40))
)
db.session.commit()

click.echo("Created users:")
for id, username in enumerate(usernames, start=1):
click.echo(f"{id}. {username}")
40 changes: 39 additions & 1 deletion backend/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from sqlalchemy import or_

from backend.extensions import db
from backend.models import Hacknight, Participant, User
from backend.models import Hacknight, Participant, Team, User


class BaseFactory(factory.alchemy.SQLAlchemyModelFactory):
Expand Down Expand Up @@ -98,3 +98,41 @@ def participants(self, create, extracted, **kwargs):
if extracted:
for participant in extracted:
self.participants.append(participant)


class TeamFactory(BaseFactory):
class Meta:
model = Team

project_name = factory.Faker("word", locale="pl_PL")
description = factory.Faker("paragraph", locale="pl_PL")
project_url = factory.LazyAttribute(
lambda obj: f"https://{obj.project_name}.codeforpoznan.test"
)

@classmethod
def _create(cls, model_class, *args, **kwargs):
session = cls._meta.sqlalchemy_session
with session.no_autoflush:
existing = (
session.query(model_class)
.filter_by(project_name=kwargs["project_name"])
.first()
)

if existing:
project_name = cls.project_name.generate({})
kwargs["project_name"] = project_name
kwargs["project_url"] = f"https://{project_name}.codeforpoznan.test"

obj = super(TeamFactory, cls)._create(model_class, *args, **kwargs)

return obj

@factory.post_generation
def members(self, create, extracted, **kwargs):
if not create:
return
if extracted:
for member in extracted:
self.members.append(member)
129 changes: 129 additions & 0 deletions backend/resources/team.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
from http import HTTPStatus

from flask import request

from flask_jwt_extended import jwt_required
from flask_restful import Resource

from marshmallow import fields, Schema, ValidationError

from backend.extensions import db
from backend.models import Participant, Team
from backend.serializers.team_serializer import TeamSchema


class TeamList(Resource):
@jwt_required
def get(self):
team_schema = TeamSchema(many=True, exclude=("members",))
return (
team_schema.dump(Team.query.all()),
HTTPStatus.OK,
)

@jwt_required
def post(self):
team_schema = TeamSchema()
json_data = request.get_json(force=True)
if not json_data:
return {"message": "No input data provided"}, HTTPStatus.BAD_REQUEST
try:
data = team_schema.load(json_data)
except ValidationError as err:
return (err.messages), HTTPStatus.BAD_REQUEST

if Team.query.filter_by(project_name=data["project_name"]).first():
return (
{"message": "Team with that project_name already exists."},
HTTPStatus.CONFLICT,
)
if Team.query.filter_by(project_url=data["project_url"]).first():
return (
{"message": "Team with that project_url already exists."},
HTTPStatus.CONFLICT,
)
team = Team(**data)
db.session.add(team)
db.session.commit()

return team_schema.dump(team), HTTPStatus.CREATED


class TeamDetails(Resource):
@jwt_required
def get(self, id):
team_schema = TeamSchema()
return team_schema.dump(Team.query.get_or_404(id)), HTTPStatus.OK

@jwt_required
def delete(self, id):
team = Team.query.get_or_404(id)
db.session.delete(team)
db.session.commit()
return HTTPStatus.OK

@jwt_required
def put(self, id):
team_schema = TeamSchema(partial=True)
team = Team.query.get_or_404(id)
json_data = request.get_json(force=True)
try:
data = team_schema.load(json_data)
except ValidationError as err:
return err.messages, HTTPStatus.BAD_REQUEST

for key, value in data.items():
setattr(team, key, value)
db.session.add(team)
db.session.commit()
return team_schema.dump(team), HTTPStatus.OK


class TeamMembers(Resource):
@jwt_required
def post(self, id):
team = Team.query.get_or_404(id)
members = [member.id for member in team.members]

json_data = request.get_json(force=True)
ids_schema = Schema.from_dict({"members_ids": fields.List(fields.Int())})
try:
data = ids_schema().load(json_data)
except ValidationError as err:
return err.messages, HTTPStatus.UNPROCESSABLE_ENTITY

new_members = [_id for _id in data["members_ids"] if _id not in members]
if not new_members:
return (
{"message": "No new member has been provided"},
HTTPStatus.BAD_REQUEST,
)

for new_member in new_members:
team.members.append(Participant.query.get_or_404(new_member))
db.session.add(team)
db.session.commit()

team_schema = TeamSchema()
return team_schema.dump(team), HTTPStatus.OK

@jwt_required
def delete(self, id):
team = Team.query.get_or_404(id)
members = {member.id for member in team.members}

json_data = request.get_json(force=True)
ids_schema = Schema.from_dict({"members_ids": fields.List(fields.Int())})
try:
data = ids_schema().load(json_data)
except ValidationError as err:
return err.messages, HTTPStatus.UNPROCESSABLE_ENTITY

to_remove = members.intersection(set(data["members_ids"]))
if not to_remove:
return {"message": "No member to delete"}, HTTPStatus.BAD_REQUEST

team.members = [member for member in team.members if member.id not in to_remove]
db.session.commit()
team_schema = TeamSchema()
return team_schema.dump(team), HTTPStatus.OK
13 changes: 13 additions & 0 deletions backend/serializers/team_serializer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from marshmallow import Schema, fields, validate


class TeamSchema(Schema):
class Meta:
fields = ("id", "project_name", "description", "project_url", "members")

dump_only = ("id", "members")

project_name = fields.Str(required=True, validate=[validate.Length(min=3, max=50)])
description = fields.Str()
project_url = fields.Str(validate=[validate.Length(max=200)])
members = fields.Nested("ParticipantSchema", exclude=("hacknights",), many=True)
15 changes: 14 additions & 1 deletion backend/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from backend.app import create_app
from backend.extensions import db
from backend.factories import HacknightFactory, ParticipantFactory
from backend.factories import HacknightFactory, ParticipantFactory, TeamFactory
from backend.models import User


Expand Down Expand Up @@ -161,3 +161,16 @@ def add_hacknights(app, _db):
def add_participants_to_hacknight(app, _db):
HacknightFactory(participants=ParticipantFactory.create_batch(10))
_db.session.commit()


@pytest.fixture
def add_teams(app, _db):
for _ in range(10):
TeamFactory.create()
_db.session.commit()


@pytest.fixture
def add_members_to_team(app, _db):
TeamFactory(members=ParticipantFactory.create_batch(10))
_db.session.commit()
4 changes: 3 additions & 1 deletion backend/tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from backend.commands.remove_expired_tokens import remove_expired_tokens
from backend.commands.populate_database import populate_database
from backend.models import Hacknight, JWTToken, Participant, User
from backend.models import Hacknight, JWTToken, Participant, Team, User
from backend.resources.auth import add_token_to_database


Expand All @@ -25,10 +25,12 @@ def test_populate_database(script):
assert "Creating 30 hacknights" in result.output
assert "Creating 50 participants" in result.output
assert "Creating 5 users" in result.output
assert "Creating 5 teams" in result.output
assert "Created users:" in result.output
with app.app_context():
assert len(Hacknight.query.all()) == 30
assert len(Participant.query.all()) == 50
assert len(Team.query.all()) == 5
assert len(User.query.all()) == 5


Expand Down
Loading

0 comments on commit 409423c

Please sign in to comment.