Skip to content

Commit

Permalink
Merge branch 'dev' into apis/update-post-users
Browse files Browse the repository at this point in the history
  • Loading branch information
abrahmasandra authored Nov 30, 2023
2 parents 657e810 + 721ef60 commit 730f554
Show file tree
Hide file tree
Showing 12 changed files with 373 additions and 17 deletions.
9 changes: 8 additions & 1 deletion src/chigame/games/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django import forms

from .models import Game
from .models import Game, Lobby


class GameForm(forms.ModelForm):
Expand All @@ -18,3 +18,10 @@ class Meta:
"name": forms.TextInput,
"image": forms.Textarea(attrs={"cols": 80, "rows": 1}),
}


class LobbyForm(forms.ModelForm):
class Meta:
model = Lobby
fields = ["name", "game", "game_mod_status", "min_players", "max_players", "time_constraint"]
widgets = {"name": forms.TextInput}
7 changes: 6 additions & 1 deletion src/chigame/games/urls.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
from django.urls import path

from . import views
from .views import LobbyListView
from .views import LobbyCreateView, LobbyListView

urlpatterns = [
# lobbies
path("lobby/", LobbyListView.as_view(), name="lobby-list"),
path("lobby/create/", LobbyCreateView.as_view(), name="lobby-create"),
path("lobby/<int:pk>/", views.ViewLobbyDetails.as_view(), name="lobby-details"),
path("lobby/<int:pk>/join", views.lobby_join, name="lobby-join"),
path("lobby/<int:pk>/leave", views.lobby_leave, name="lobby-leave"),
path("lobby/<int:pk>/edit/", views.LobbyUpdateView.as_view(), name="lobby-edit"),
path("lobby/<int:pk>/delete/", views.LobbyDeleteView.as_view(), name="lobby-delete"),
# games
path("", views.GameListView.as_view(), name="game-list"),
path("create/", views.GameCreateView.as_view(), name="game-create"),
path("<int:pk>/edit", views.GameEditView.as_view(), name="game-edit"),
Expand Down
47 changes: 45 additions & 2 deletions src/chigame/games/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@

from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import UserPassesTestMixin
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.http import HttpResponseForbidden
from django.shortcuts import get_object_or_404, redirect, render, reverse
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.generic import CreateView, DeleteView, DetailView, ListView, UpdateView
from django_tables2 import SingleTableView

from .forms import GameForm
from .forms import GameForm, LobbyForm
from .models import Game, Lobby, Tournament
from .tables import LobbyTable

Expand Down Expand Up @@ -53,12 +55,53 @@ def lobby_leave(request, pk):
return redirect(reverse("lobby-details", kwargs={"pk": lobby.id}))


class LobbyCreateView(LoginRequiredMixin, CreateView):
model = Lobby
form_class = LobbyForm
template_name = "games/lobby_form.html"
success_url = reverse_lazy("lobby-list")

def form_valid(self, form):
form.instance.created_by = self.request.user
form.instance.lobby_created = timezone.now()
return super().form_valid(form)


class ViewLobbyDetails(DetailView):
model = Lobby
template_name = "games/lobby_details.html"
context_object_name = "lobby_detail"


class LobbyUpdateView(UpdateView):
model = Lobby
form_class = LobbyForm
template_name = "games/lobby_form.html"

def get_success_url(self):
return reverse_lazy("lobby-details", kwargs={"pk": self.object.pk})

def dispatch(self, request, *args, **kwargs):
# get the lobby object
self.object = self.get_object()
# check if the user making the request is the "host" of the lobby
if request.user != self.object.created_by and not request.user.is_staff:
return HttpResponseForbidden("You don't have permission to edit this lobby.")
return super().dispatch(request, *args, **kwargs)


class LobbyDeleteView(DeleteView):
model = Lobby
template_name = "games/lobby_confirm_delete.html"
success_url = reverse_lazy("lobby-list")

def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
if request.user != self.object.created_by and not request.user.is_staff:
return HttpResponseForbidden("You don't have permission to delete this lobby.")
return super().dispatch(request, *args, **kwargs)


class GameDetailView(DetailView):
model = Game
template_name = "games/game_detail.html"
Expand Down
55 changes: 54 additions & 1 deletion src/chigame/users/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
from typing import Any

from django.contrib.auth import get_user_model
from factory import Faker, post_generation
from django.contrib.contenttypes.models import ContentType
from factory import Faker, LazyAttribute, SubFactory, lazy_attribute, post_generation
from factory.django import DjangoModelFactory

from chigame.users.models import FriendInvitation, Notification


class UserFactory(DjangoModelFactory):
email = Faker("email")
Expand All @@ -29,3 +32,53 @@ def password(self, create: bool, extracted: Sequence[Any], **kwargs):
class Meta:
model = get_user_model()
django_get_or_create = ["email"]


class FriendInvitationFactory(DjangoModelFactory):
class Meta:
model = FriendInvitation

sender = SubFactory(UserFactory)
accepted = Faker("boolean")
timestamp = Faker("date_time_this_year")

@lazy_attribute
def receiver(self):
return FriendInvitationFactory.get_different_user(self.sender)

@staticmethod
def get_different_user(sender):
receiver = sender
while receiver.pk == sender.pk:
receiver = UserFactory()
return receiver


class BaseNotificationFactory(DjangoModelFactory):
class Meta:
model = Notification

receiver = SubFactory(UserFactory)
first_sent = last_sent = Faker("date_time_this_year")
type = Faker(
"random_element",
elements=[
Notification.FRIEND_REQUEST,
Notification.REMINDER,
Notification.UPCOMING_MATCH,
Notification.MATCH_PROPOSAL,
Notification.GROUP_INVITATION,
],
)
read = False
visible = True
message = Faker("sentence")


class FriendInvitationNotificationFactory(BaseNotificationFactory):
class Params:
actor = SubFactory(FriendInvitationFactory)

actor = LazyAttribute(lambda x: x.actor)
actor_content_type = LazyAttribute(lambda x: ContentType.objects.get(model=x.actor._meta.model_name))
actor_object_id = LazyAttribute(lambda x: x.actor.pk)
116 changes: 115 additions & 1 deletion src/chigame/users/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,119 @@
from chigame.users.models import User
import pytest

from chigame.users.models import Notification, User

from .factories import FriendInvitationFactory, FriendInvitationNotificationFactory, UserFactory


def test_user_get_absolute_url(user: User):
assert user.get_absolute_url() == f"/users/{user.pk}/"


@pytest.mark.django_db
def test_friendinvitation_notification_attrs():
notification = FriendInvitationNotificationFactory.create()
assert notification.read is False
assert notification.visible is True
assert (
not notification.first_sent > notification.last_sent
) # not comparing equality because last_sent is created after first_sent
# and they will be different by just a little bit
notification.renew_notification()
assert notification.first_sent < notification.last_sent


@pytest.mark.django_db
def test_friendinvitation_notification_mark_x_methods():
notification = FriendInvitationNotificationFactory.create()

notification.mark_as_read()
assert notification.read is True

notification.mark_as_unread()
assert notification.read is False

notification.mark_as_deleted()
assert notification.visible is False


@pytest.mark.django_db
def test_notificationqueryset_filter_by_receiver():
user1 = UserFactory()
user2 = UserFactory()
user3 = UserFactory()
FriendInvitationNotificationFactory.create_batch(5, receiver=user1)
FriendInvitationNotificationFactory.create_batch(4, receiver=user2)
assert len(Notification.objects.filter_by_receiver(user1)) == 5
assert len(Notification.objects.filter_by_receiver(user2)) == 4
assert len(Notification.objects.filter_by_receiver(user3)) == 0


@pytest.mark.django_db
def test_notificationqueryset_filter_by_actor():
friendinvitation1 = FriendInvitationFactory()
friendinvitation2 = FriendInvitationFactory()
friendinvitation3 = FriendInvitationFactory()

FriendInvitationNotificationFactory.create_batch(5, actor=friendinvitation1)
FriendInvitationNotificationFactory.create_batch(4, actor=friendinvitation2)

assert len(Notification.objects.filter_by_actor(friendinvitation1)) == 5
assert len(Notification.objects.filter_by_actor(friendinvitation2)) == 4
assert len(Notification.objects.filter_by_actor(friendinvitation3)) == 0


@pytest.mark.django_db
def test_notificationqueryset_get_by_actor():
friendinvitation1 = FriendInvitationFactory()
friendinvitation2 = FriendInvitationFactory()
friendinvitation3 = FriendInvitationFactory()

FriendInvitationNotificationFactory.create_batch(5, actor=friendinvitation1)
FriendInvitationNotificationFactory.create_batch(1, actor=friendinvitation2)

with pytest.raises(Exception):
Notification.objects.get_by_actor(friendinvitation1)
Notification.objects.get_by_actor(friendinvitation3)

assert len(Notification.objects.filter_by_actor(friendinvitation2)) == 1


@pytest.mark.django_db
def test_notificationqueryset_mark_x_methods():
FriendInvitationNotificationFactory.create_batch(5)
notifications = Notification.objects.all()

notifications.mark_all_read()
for notification in notifications:
assert notification.read is True

notifications.mark_all_unread()
for notification in notifications:
assert notification.read is False

notifications.mark_all_deleted()
for notification in notifications:
assert notification.visible is False


@pytest.mark.django_db
def test_notificationqueryset_is_x_methods():
FriendInvitationNotificationFactory.create_batch(5)
notifications = Notification.objects.all()

assert len(Notification.objects.is_unread()) == 5
assert len(Notification.objects.is_read()) == 0
assert len(Notification.objects.is_deleted()) == 0
assert len(Notification.objects.is_not_deleted()) == 5

notifications.mark_all_read()
assert len(Notification.objects.is_unread()) == 0
assert len(Notification.objects.is_read()) == 5

notifications.mark_all_deleted()
assert len(Notification.objects.is_deleted()) == 5
assert len(Notification.objects.is_not_deleted()) == 0

notifications.restore_all_deleted()
assert len(Notification.objects.is_deleted()) == 0
assert len(Notification.objects.is_not_deleted()) == 5
11 changes: 10 additions & 1 deletion src/config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@
# https://docs.djangoproject.com/en/dev/ref/settings/#dirs
"DIRS": [
str(BASE_DIR / "templates"),
# Needed for django-machina to find the correct templates without cluttering the templates base directory
str(BASE_DIR / "templates/forum"),
# https://django-machina.readthedocs.io/en/latest/getting_started.html#django-settings
MACHINA_MAIN_TEMPLATE_DIR,
],
Expand Down Expand Up @@ -320,10 +322,17 @@
# Add additional configuration below:
# ------------------------------------------------------------------------------

# Django-machina search backend
# Django-machina search backend:
# https://django-machina.readthedocs.io/en/latest/getting_started.html#django-haystack-settings
HAYSTACK_CONNECTIONS = {
"default": {
"ENGINE": "haystack.backends.simple_backend.SimpleEngine",
},
}


# DJANGO-MACHINA SETTINGS
# ------------------------------------------------------------------------------
# https://django-machina.readthedocs.io/en/stable/settings.html
MACHINA_FORUM_NAME = "ChiGame Forums"
MACHINA_BASE_TEMPLATE_NAME = "base.html"
32 changes: 23 additions & 9 deletions src/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
<!-- Le javascript
================================================== -->
{# Placed at the top of the document so pages load faster with defer #}
{% block js %}
{% endblock js %}
{% block javascript %}
<!-- Bootstrap JS -->
<script defer
Expand Down Expand Up @@ -66,6 +68,12 @@
<li class="nav-item">
<a class="nav-link" href="{% url 'about' %}">About</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'forum:index' %}">Forums</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'lobby-list' %}">Lobbies</a>
</li>
{% if request.user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{% url 'game-list' %}">Games</a>
Expand Down Expand Up @@ -97,21 +105,25 @@
</li>
{% endif %}
<li>
<!-- Dynammically update search bar landing page depending on query_type-->
<form id="search-bar" action="{% url 'game-search-results' %}" method="get">
<select id="query-type" name="query_type">
<option value="games">Games</option>
<option value="users">Users</option>
<!-- Add more options here -->
</select>
<input id="query-input" name="query" type="text" placeholder="Search..." />
</form>
{% block search_bar %}
<!-- Dynamically update search bar landing page depending on query_type-->
<form id="search-bar" action="{% url 'game-search-results' %}" method="get">
<select id="query-type" name="query_type">
<option value="games">Games</option>
<option value="users">Users</option>
<!-- Add more options here -->
</select>
<input id="query" name="query" type="text" placeholder="Search..." />
</form>
{% endblock search_bar %}
</li>
</ul>
</div>
</div>
</nav>
</div>
{% block body %}
{% endblock body %}
<div class="container">
{% if messages %}
{% for message in messages %}
Expand Down Expand Up @@ -164,6 +176,8 @@
search_bar.action = "{% url 'game-search-results' %}";
} else if (query_type.value == "users") {
search_bar.action = "{% url 'users:user-search-results' %}";
} else if (query_type.value == "forums") {
search_bar.action = "{% url 'home' %}"; // placeholder
}
});
});
Expand Down
Loading

0 comments on commit 730f554

Please sign in to comment.