Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[301] teams endpoints (fixes#301) #311

Merged
merged 6 commits into from
Oct 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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