diff --git a/game_event/models.py b/game_event/models.py index 8ff5418..23d502d 100644 --- a/game_event/models.py +++ b/game_event/models.py @@ -51,3 +51,9 @@ def validate_game_event(court, ball_game, min_number_of_players, max_number_of_p @staticmethod def is_ball_game_playable_at_court(court, ball_game): return CourtBallGame.is_ball_game_playable(court, ball_game) + + def is_event_full(self): + from game_event_player.models import GameEventPlayer + + current_players = GameEventPlayer.objects.filter(game_event=self).count() + return current_players == self.max_number_of_players diff --git a/game_event/templates/game_event/game-event.html b/game_event/templates/game_event/game-event.html index 3a72511..44382eb 100644 --- a/game_event/templates/game_event/game-event.html +++ b/game_event/templates/game_event/game-event.html @@ -2,32 +2,49 @@ {% block content %} {% load static %} + - + - Game Event - - - - {% if time %} -

Event Details

-

Time: {{ time }}

-

Level: {{ level_of_game }}

-

Min Players: {{ min_number_of_players }}

-

Max Players: {{ max_number_of_players }}

-

Court: {{ court }} {{ neighborhood }}

-

Game: {{ ball_game }}

-

Players:

- - - + + + + + + + {% if time %} +

Event Details

+

Time: {{ time }}

+

Level: {{ level_of_game }}

+

Min Players: {{ min_number_of_players }}

+

Max Players: {{ max_number_of_players }}

+

Court: {{ court }} {{ neighborhood }}

+

Game: {{ ball_game }}

+

Players:

+
+ + - + {% for player in event_players %} @@ -46,46 +63,40 @@

Players:

{% endfor %}
Name Brings Ball
-
{% if in_event %} - - Remove Event - -
-

Chat With Players!

- - - {% if all_messages %} -
-
- {% for message in all_messages %} - - {% if message.user_id.user.username == request.user.username %} -
- {{ message.time_sent|date:"F d, H:i" }} - {{ message.user_id.user.username }}
-

{{ message.text }}

-
+ +
+

Chat With Players!

+ + {% if all_messages %} +
+
+ {% for message in all_messages %} + {% if message.user_id.user.username == request.user.username %} +
+ {{ message.time_sent|date:"F d, H:i" }} + {{ message.user_id.user.username }}
+

{{ message.text }}

+
{% else %} -
- {{ message.time_sent|date:"F d, H:i" }} - {{ message.user_id.user.username }}
-

{{ message.text }}

-
+
+ {{ message.time_sent|date:"F d, H:i" }} + {{ message.user_id.user.username }}
+

{{ message.text }}

+
{% endif %} - {% endfor %} - - -
-
- + {% endfor %} +
+
{% else %} + +
{% endif %}
@@ -95,7 +106,7 @@

Chat With Players!

- - {% if messages %} -
-
- -
-
- {% endif %} - + - - -
+
{% else %} - - Join Event +
+ + Join Event +
{% endif %} - + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} {% else %}

Event Does not exist.

@@ -140,4 +139,4 @@

Event Does not exist.

{% endif %} -{%endblock%} +{%endblock%} \ No newline at end of file diff --git a/game_event/templates/game_event/join-event.html b/game_event/templates/game_event/join-event.html index 53cc387..255f97f 100644 --- a/game_event/templates/game_event/join-event.html +++ b/game_event/templates/game_event/join-event.html @@ -3,18 +3,21 @@ + + - + {% if event_exists %} -

Game Event

+
+

Join Game Event

{% csrf_token %} -

Are you bringing a ball?

-
{% else %} -
+

Can not join, Event Does Not exist!

{% endif %} diff --git a/game_event/templates/game_event/result.html b/game_event/templates/game_event/result.html deleted file mode 100644 index 655f4c8..0000000 --- a/game_event/templates/game_event/result.html +++ /dev/null @@ -1,19 +0,0 @@ -{% load static %} - - - - - - - - {% if event_exists == False %} -

Can not remove from event, Event Does not Exist!

- {% elif reached_max == True %} -

Max number of Player reached! You can't join

- {% elif in_event %} -

You joined the event!

- {% else %} -

You are not in the event!

- {% endif %} - - diff --git a/game_event/tests.py b/game_event/tests.py index 35749e3..23c848f 100644 --- a/game_event/tests.py +++ b/game_event/tests.py @@ -8,11 +8,9 @@ from .views import process_game_event_form, get_weather_from_api from datetime import timedelta import pytest -from django.db import IntegrityError from unittest import mock import urllib - TEST_ID = 2 TEST_TIME = timezone.now() TEST_LEVEL = 3 @@ -232,7 +230,7 @@ def test_fail_loading_site_game_event(self, client, player): def test_success_loading_site_join_game_event(self, client, game_event, player): client.force_login(player.user) url = reverse('join_event', args=[game_event.id]) - response = client.get(url) + response = client.get(url, follow=True) assert response.status_code == 200 def test_fail_loading_site_join_game_event(self, client, player): @@ -244,30 +242,30 @@ def test_fail_loading_site_join_game_event(self, client, player): def test_success_loading_site_remove_game_event(self, client, game_event, game_event_player): client.force_login(game_event_player.player.user) url = reverse('remove_from_event', args=[game_event.id]) - response = client.get(url) + response = client.get(url, follow=True) assert response.status_code == 200 def test_fail_loading_site_remove_game_event_from_player(self, client, player): client.force_login(player.user) - response = client.get('/game-events/remove-from-event/-1/') + response = client.get('/game-events/remove-from-event/-1/', follow=True) assert response.status_code == 200 - assert b"Can not remove from event, Event Does not Exist! " in response.content + assert b"Event Does not exist." in response.content def test_fail_loading_remove_from_event_after_deleting_the_game_event(self, client, game_event, game_event_player): client.force_login(game_event_player.player.user) url = reverse('remove_from_event', args=[game_event.id]) - response = client.get(url) + response = client.get(url, follow=True) assert response.status_code == 200 game_event.delete() - response = client.get(url) + response = client.get(url, follow=True) assert response.status_code == 200 - assert b"Can not remove from event, Event Does not Exist! " in response.content + assert b"Event Does not exist." in response.content def test_remove_player_from_event(self, client, game_event, game_event_player): client.force_login(game_event_player.player.user) url = reverse('remove_from_event', args=[game_event.id]) - response = client.get(url) + response = client.get(url, follow=True) assert response.status_code == 200 with pytest.raises(ObjectDoesNotExist): GameEventPlayer.objects.get(game_event=game_event, player=game_event_player.player) @@ -281,7 +279,7 @@ def test_remove_player_that_is_not_in_event(self, client, game_event, player): def test_add_player_to_event(self, client, game_event, player): client.force_login(player.user) url = reverse('process_answer_game_event', args=[game_event.id]) - response = client.post(url, {'answer': 'yes'}) + response = client.post(url, {'answer': 'yes'}, follow=True) assert response.status_code == 200 assert response.context['in_event'] assert GameEventPlayer.objects.filter(game_event=game_event, player=player).exists() @@ -289,16 +287,35 @@ def test_add_player_to_event(self, client, game_event, player): def test_fail_add_player_to_event_already_in_it(self, client, game_event, player): client.force_login(player.user) url = reverse('process_answer_game_event', args=[game_event.id]) - response = client.post(url, {'answer': 'yes'}) + response = client.post(url, {'answer': 'yes'}, follow=True) + assert response.status_code == 200 + url = reverse('join_event', args=[game_event.id]) + response = client.get(url, follow=True) + assert response.status_code == 200 + assert b"Already in event!" in response.content + + def test_player_can_not_join_full_event(self, client, five_game_event_players, player, game_event): + client.force_login(player.user) + url = reverse('join_event', args=[game_event.id]) + response = client.get(url, follow=True) assert response.status_code == 200 - with pytest.raises(IntegrityError): - response = client.post(url, {'answer': 'yes'}) + assert b"The event is full! can not join" in response.content + + def test_event_time_constraint(self, client, game_event, game_event_player, court, court_ball_game): + client.force_login(game_event_player.player.user) + event = GameEvent.create(time=game_event.time + timedelta(hours=2), level_of_game=0, + min_number_of_players=2, max_number_of_players=5, court=court, + ball_game=BallGame.Basketball) + url = reverse('join_event', args=[event.id]) + response = client.get(url, follow=True) + assert response.status_code == 200 + assert b"You have scheduling conflicts. Resolve them before joining the event" in response.content def test_event_deletion_on_last_player_leave(self, client, game_event, game_event_player): count_game_events_before = GameEvent.objects.count() client.force_login(game_event_player.player.user) url = reverse('remove_from_event', args=[game_event.id]) - response = client.get(url) + response = client.get(url, follow=True) assert response.status_code == 200 assert count_game_events_before - 1 == GameEvent.objects.count() with pytest.raises(ObjectDoesNotExist): diff --git a/game_event/views.py b/game_event/views.py index 6274b72..58fdd18 100644 --- a/game_event/views.py +++ b/game_event/views.py @@ -95,6 +95,12 @@ def game_event(request, id): } return render(request, 'game_event/game-event.html', context) + context = add_content(event, player) + + return render(request, 'game_event/game-event.html', context) + + +def add_content(event, player): event_players = [ { "first_name": entry.player.user.first_name, @@ -118,15 +124,34 @@ def game_event(request, id): 'event_players': event_players, 'all_messages': all_messages } - return render(request, 'game_event/game-event.html', context) + return context def join_event(request, id): try: - GameEvent.objects.get(pk=id) + event = GameEvent.objects.get(pk=id) except GameEvent.DoesNotExist: return render(request, 'game_event/join-event.html', {'id': id, 'event_exists': False}) + player = Player.objects.get(user=request.user) + if GameEventPlayer.objects.filter(game_event=event, player=player).exists(): + messages.error(request, "Already in event!") + return redirect(f"/game-events/{id}", add_content(event, player)) + + is_event_full = event.is_event_full() + is_event_time_available = player.is_event_time_available(event.time) + + error_messages = [] + if is_event_full: + error_messages.append("The event is full! can not join") + if not is_event_time_available: + error_messages.append("You have scheduling conflicts. Resolve them before joining the event") + + if error_messages: + for error in error_messages: + messages.error(request, error) + return redirect(f"/game-events/{id}", {}) + return render(request, 'game_event/join-event.html', {'id': id, 'event_exists': True}) @@ -134,32 +159,27 @@ def remove_from_event(request, id): try: event = GameEvent.objects.get(pk=id) except GameEvent.DoesNotExist: - return render(request, 'game_event/result.html', {'event_exists': False}) + return redirect(f"/game-events/{id}", {}) player = Player.objects.get(user=request.user) GameEventPlayer.remove_player_and_check_event_deletion(event, player) - return render(request, 'game_event/result.html') + messages.success(request, 'You are removed from the event') + return redirect(f"/game-events/{id}", add_content(event, player)) def process_answer_game_event(request, id): try: event = GameEvent.objects.get(pk=id) except GameEvent.DoesNotExist: - return render(request, 'game_event/result.html', {'event_exists': False}) - + return redirect(f"/game-events/{id}", {}) + content = {} player = Player.objects.get(user=request.user) ball_responsible = request.POST.get('answer') == 'yes' - content = {} - num_of_players_in_event = len(GameEventPlayer.objects.filter(game_event=event)) - if num_of_players_in_event < event.max_number_of_players: - GameEventPlayer.objects.create(game_event=event, player=player, ball_responsible=ball_responsible).save() - in_event = GameEventPlayer.objects.filter(game_event=event, player=player).exists() - content["in_event"] = in_event - content["reached_max"] = False - else: - content["reached_max"] = True - content['event_exists'] = True - return render(request, 'game_event/result.html', content) + GameEventPlayer.objects.create(game_event=event, player=player, ball_responsible=ball_responsible).save() + messages.success(request, "You joined the event") + + content.update(add_content(event, player)) + return redirect(f"/game-events/{id}", content) def get_weather_from_api(game_events, weather_getter): diff --git a/message/tests.py b/message/tests.py index 4b4db8e..87c17da 100644 --- a/message/tests.py +++ b/message/tests.py @@ -109,10 +109,10 @@ def test_messages_persist_after_join_and_remove(self, client, game_event, player client.force_login(player.user) game_event_id = game_event.id url = reverse('process_answer_game_event', args=[game_event_id]) - response = client.post(url, {'answer': 'yes'}) + response = client.post(url, {'answer': 'yes'}, follow=True) assert response.status_code == 200 url = reverse('save_text_message', args=[game_event_id]) - client.post(url, {'mytextbox': 'hi from user'}) + client.post(url, {'mytextbox': 'hi from user'}, follow=True) reverse('remove_from_event', args=[game_event_id]) player_messages = [entry.text for entry in Message.objects.filter(game_event_id=game_event)] assert 'hi from user' in player_messages diff --git a/player/models.py b/player/models.py index ea62e2a..8478a0e 100644 --- a/player/models.py +++ b/player/models.py @@ -2,6 +2,7 @@ from django.db import models import datetime from django.core.exceptions import ValidationError +from django.utils import timezone class BallGame(models.TextChoices): @@ -65,3 +66,13 @@ def validate_and_save(self, birth_date, favorite_ball_game, profile_picture=None else: self.save() + + def is_event_time_available(self, desired_event_time): + from game_event_player.models import GameEventPlayer + TIME_LIMIT = 4 + start_time_limit = desired_event_time - timezone.timedelta(hours=TIME_LIMIT) + end_time_limit = desired_event_time + timezone.timedelta(hours=TIME_LIMIT) + conflicting_events = GameEventPlayer.objects.filter(player=self, + game_event__time__range=(start_time_limit, + end_time_limit)) + return not conflicting_events.exists() diff --git a/static/css/chat.css b/static/css/chat.css index ff6b22d..e5953b0 100644 --- a/static/css/chat.css +++ b/static/css/chat.css @@ -1,31 +1,30 @@ @import url('https://fonts.googleapis.com/css?family=Red+Hat+Display:400,500,900&display=swap'); - .container { - position: absolute; - } - - - .top-right { - position: absolute; - top: 0; - right: 0; - left: 700px - } - - .message-box { - border: none; - background-color: #f5f5f5; - box-shadow: +.container { + position: absolute; +} + +.top-right { + position: absolute; + top: 0; + right: 0; + left: 700px +} + +.message-box { + border: none; + background-color: #f5f5f5; + box-shadow: 0 0 1rem rgba(0, 0, 0, 0.1), 0rem 1rem 1rem -1rem rgba(0, 0, 0, 0.2); - padding: 10px; - margin-bottom: 10px; - height: 300px; - width: 400px; - overflow-y: auto; - } - - .message-box::-webkit-scrollbar { + padding: 10px; + margin-bottom: 10px; + height: 300px; + width: 400px; + overflow-y: auto; + } + +.message-box::-webkit-scrollbar { width: 8px; } @@ -43,104 +42,100 @@ } - .message-container { - border: 1px solid #ccc; - padding: 10px; - margin: 3px 10px; - overflow-wrap: break-word; +.message-container { + border: 1px solid #ccc; + padding: 10px; + margin: 3px 10px; + overflow-wrap: break-word; background-color: #F7F7F7; box-sizing: border-box; width: fit-content; float: right; - height: fit-content; - text-align: left; - font-size: 0.9rem; - padding-top: 0.125rem; - border-radius: 1.125rem 1.125rem 0 1.125rem; - top: 0; - left: 2.5rem; - background: #626161; - color: white; - clear: both; - - } - - .message-other-user-container { - box-sizing: border-box; - padding: 10px; - margin: 3px 10px; - background: #f1ebeb; - overflow-wrap: break-word; - border-radius: 1.125rem 1.125rem 1.125rem 0; - min-height: 2.25rem; - width: fit-content; - float: left; - clear: both; - max-width: 66%; - left: 2.5rem; - box-shadow: - 0 0 2rem rgba(black, 0.075), - 0rem 1rem 1rem -1rem rgba(black, 0.1); - - } + height: fit-content; + text-align: left; + font-size: 0.9rem; + padding-top: 0.125rem; + border-radius: 1.125rem 1.125rem 0 1.125rem; + top: 0; + left: 2.5rem; + background: #626161; + color: white; + clear: both; +} + +.message-other-user-container { + box-sizing: border-box; + padding: 10px; + margin: 3px 10px; + background: #f1ebeb; + overflow-wrap: break-word; + border-radius: 1.125rem 1.125rem 1.125rem 0; + min-height: 2.25rem; + width: fit-content; + float: left; + clear: both; + max-width: 66%; + left: 2.5rem; + box-shadow: + 0 0 2rem rgba(black, 0.075), + 0rem 1rem 1rem -1rem rgba(black, 0.1); +} - .message-time { - color: #EEE; - font-size: 8px; - } +.message-time { + color: #EEE; + font-size: 8px; +} - .message-other-time { - color: #333; - font-size: 8px; - } +.message-other-time { + color: #333; + font-size: 8px; +} - - .name { - font-weight: 500; - margin-bottom: 0.125rem; +.name { + font-weight: 500; + margin-bottom: 0.125rem; } - .name-other { - font-weight: 500; - color:#7a7474; - margin-bottom: 0.125rem; - } +.name-other { + font-weight: 500; + color:#7a7474; + margin-bottom: 0.125rem; +} - .message { - font-size: 0.9rem; - color: #cbc2c2; - } +.message { + font-size: 0.9rem; + color: #cbc2c2; +} - .message-other { - font-size: 0.9rem; - color: #6c6767; - } +.message-other { + font-size: 0.9rem; + color: #6c6767; +} - input { - border:none; - background-image:none; - background-color: white; - padding: 0.5rem 1rem; - margin-right: 1rem; - border-radius: 1.125rem; - flex-grow: 2; - box-shadow: - 0 0 1rem rgba(black, 0.1), - 0rem 1rem 1rem -1rem rgba(black, 0.2); +input { + border:none; + background-image:none; + background-color: white; + padding: 0.5rem 1rem; + margin-right: 1rem; + border-radius: 1.125rem; + flex-grow: 2; + box-shadow: + 0 0 1rem rgba(black, 0.1), + 0rem 1rem 1rem -1rem rgba(black, 0.2); + font-family: Red hat Display, sans-serif; + font-weight: 400; + letter-spacing: 0.025em; + width: 15rem; - font-family: Red hat Display, sans-serif; - font-weight: 400; - letter-spacing: 0.025em; - width: 15rem; - - &:placeholder { - color: #999; - } - } + &:placeholder { + color: #999; + } +} - button { +button { border: none; background-image: none; background-color: white; diff --git a/static/css/game-event.css b/static/css/game-event.css index 6ee9282..34532c4 100644 --- a/static/css/game-event.css +++ b/static/css/game-event.css @@ -8,9 +8,10 @@ display: inline-block; background-color: #a7898e; color: #ece2db; - padding: 10px 20px; + padding: 13px 20px; border-radius: 20px; text-decoration: none; + border: none; } .round-button:hover { color: #a7898e; @@ -20,7 +21,7 @@ width: 30%; border-collapse: collapse; margin-top: 10px; - border-radius: 10px; /* Adjust the value as per your preference */ + border-radius: 10px; overflow: hidden; } @@ -40,3 +41,43 @@ width: 20px; height: 20px; } + +.button-container { + margin-top: 50px; + display: flex; + flex-direction: column; +} + +.button-container .round-button { + margin-bottom: 20px; + width: 150px; + font-size: 16px; + display: flex; + justify-content: center; + font-weight: bold; + align-items: center; +} + +.table-container { + margin-top: 30px; +} + +.message-container-general.success { + color: #80c698; +} + +.message-container-general.error { + color: #fc7c7c; +} + +.message-container-general { + margin-top: 10px; + padding: 15px; + margin-bottom: 5px; + font-size: 20px; + font-weight: bold; + font-family: "Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif; + text-align: left; + width: fit-content; + line-height: 0.2; +} diff --git a/static/css/join-game-event.css b/static/css/join-game-event.css new file mode 100644 index 0000000..ff34bc7 --- /dev/null +++ b/static/css/join-game-event.css @@ -0,0 +1,32 @@ +body { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + font-family: "Arial", sans-serif; + background-color: #fcf3f0; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + margin-top: -200px; +} + +h1 { + font-size: 45px; + color: #AB8E90; +} + +.other-text { + font-family: "Arial", sans-serif; + font-size: 24px; + color: #333; + align-items: center; + cursor: pointer; +} + +.other-text input[type="radio"] { + margin-right: 20px; /* Adjust the margin as desired */ +} \ No newline at end of file