Skip to content

Commit

Permalink
Merge pull request #292 from bounswe/equipment-posts-v2
Browse files Browse the repository at this point in the history
Equipment posts API (resolves #268)
  • Loading branch information
ege-kaya committed Dec 30, 2021
2 parents 86ab71e + e4d9336 commit 1fab30e
Show file tree
Hide file tree
Showing 14 changed files with 320 additions and 13 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/webapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ jobs:
run: |
python manage.py makemigrations authentication
python manage.py makemigrations eventposts
python manage.py makemigrations equipmentposts
python manage.py migrate
- name: Run tests
run: py.test
run: py.test
3 changes: 2 additions & 1 deletion backend/app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@
'rest_framework',
'authentication',
'django_rest_passwordreset',
'equipmentposts',
'eventposts',
'profiles',
'frontend'
'frontend',
]

SITE_ID = 1
Expand Down
3 changes: 2 additions & 1 deletion backend/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@
path('api/', include('authentication.urls')),
path('api/', include('eventposts.urls')),
path('api/', include('profiles.urls')),
path('', include('frontend.urls'))
path('api/', include('equipmentposts.urls')),
path('', include('frontend.urls')),
]
5 changes: 3 additions & 2 deletions backend/authentication/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime
import datetime
from django.contrib.auth.models import AbstractUser
from django.dispatch import receiver
from django.db import models
Expand All @@ -25,7 +25,8 @@ def password_reset_token_created(sender, instance, reset_password_token, *args,
class User(AbstractUser):
bio = models.TextField(default="")

birthday = models.DateField(default=datetime.now, blank=True)
birthday = models.DateField(default=datetime.date(datetime.date.today().year - 18,
datetime.date.today().month, datetime.date.today().day), blank=True)

avatar = models.TextField(default="")
location = models.TextField(default="")
Expand Down
Empty file.
3 changes: 3 additions & 0 deletions backend/equipmentposts/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions backend/equipmentposts/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class EquipmentpostsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'equipmentposts'
11 changes: 11 additions & 0 deletions backend/equipmentposts/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.db import models
from eventposts.models import Post
# Create your models here.


class EquipmentPost(Post):
url = models.TextField(default="")
equipment_type = models.TextField()

class Meta:
app_label = 'equipmentposts'
7 changes: 7 additions & 0 deletions backend/equipmentposts/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from rest_framework import serializers
from equipmentposts.models import EquipmentPost

class EquipmentSerializer(serializers.ModelSerializer):
class Meta:
model = EquipmentPost
fields = "__all__"
105 changes: 105 additions & 0 deletions backend/equipmentposts/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from django.test import TestCase
from rest_framework.test import force_authenticate
from rest_framework.test import APITestCase, APIRequestFactory
from rest_framework import status
from equipmentposts.models import EquipmentPost
from authentication.models import User
from equipmentposts.serializers import EquipmentSerializer
from django.urls import reverse
from django.db.models import Q
from datetime import datetime, timedelta


class EquipmentPostSearchTests(APITestCase):
def setUp(self):
# create user and get auth token
self.user = User.objects.create_user(email="user@user.com", password="1234567", username="user")
resp = self.client.post(reverse('token_create'), {'username': 'user', 'password': '1234567'})
self.token = resp.data['access']

# create additional users for spectators and players
self.user2 = User.objects.create_user(email="user2@user.com", password="1234567", username="user2")
self.user3 = User.objects.create_user(email="user3@user.com", password="1234567", username="user3")

# create mock equipments for testing
self.equipment_01 = EquipmentPost.objects.create(owner=self.user, title='Football for sale',
content='Selling WC 2010 Jabulani football',
location='Madrid', sport='Football', equipment_type="ball",
min_skill_level=3, max_skill_level=4, latitude=40.43103333341609,
longitude=-3.705507357022727)

self.equipment_02 = EquipmentPost.objects.create(owner=self.user, title='Basketball shoes for sale',
content='Selling my Air Jordans',
location='levent', sport='Basketball', equipment_type="shoes",
min_skill_level=0, max_skill_level=5, latitude=41.08204996728227,
longitude=29.016445404346598)

self.equipment_03 = EquipmentPost.objects.create(owner=self.user, title='NBA ball or something idk',
content='Selling basketball with NBA logo',
location='Washington', sport='Basketball', equipment_type="ball",
min_skill_level=3, max_skill_level=5, latitude=38.90785448747658,
longitude=-77.04329853399994)

self.equipment_04 = EquipmentPost.objects.create(owner=self.user, title='Vintage Nike mercurial football cleats',
content='worn only once',
location='etiler', sport='Football', equipment_type="shoes",
min_skill_level=3, max_skill_level=4, latitude=41.13274943188016,
longitude=29.105688623416825)

self.basketball_ads = EquipmentSerializer(EquipmentPost.objects.filter(sport="Basketball"), many=True).data

self.football_ads = EquipmentSerializer(EquipmentPost.objects.filter(sport="Football"), many=True).data

self.in_turkey_ads = EquipmentSerializer(EquipmentPost.objects.filter(
Q(location="levent") | Q(location="etiler")), many=True).data

self.ads_created_by_user = EquipmentSerializer(EquipmentPost.objects.all(), many=True).data
self.creation_date_today = EquipmentSerializer(EquipmentPost.objects.all(), many=True).data

self.skill_between_2_4 = self.football_ads

def test_filter_by_query(self):
response = self.client.get(reverse('equipmentpost-list'), {'query': 'basketball'}, HTTP_AUTHORIZATION=f'JWT {self.token}')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'], self.basketball_ads)

def test_filter_by_creation_date(self):
response = self.client.get(reverse('equipmentpost-list'), {'min_creation_date': (datetime.today() - timedelta(3)),
'max_creation_date': (datetime.today() + timedelta(1))},
HTTP_AUTHORIZATION=f'JWT {self.token}')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'], self.creation_date_today)

def test_filter_by_location(self):
response = self.client.get(reverse('equipmentpost-list'), {'location': 'le'},
HTTP_AUTHORIZATION=f'JWT {self.token}')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'], self.in_turkey_ads)

def test_filter_by_sport(self):
response = self.client.get(reverse('equipmentpost-list'), {'sport': 'Basketball'},
HTTP_AUTHORIZATION=f'JWT {self.token}')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'], self.basketball_ads)

def test_filter_by_owner(self):
response = self.client.get(reverse('equipmentpost-list'), {'owner_id': self.user.id},
HTTP_AUTHORIZATION=f'JWT {self.token}')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'], self.ads_created_by_user)

def test_filter_by_coordinates(self):
response = self.client.get(reverse('equipmentpost-list'), {'min_latitude': 36.23763062438484,
'max_latitude': 42.01901802424485,
'min_longitude': 26.732105369671633,
'max_longitude': 44.3513027746188},
HTTP_AUTHORIZATION=f'JWT {self.token}')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'], self.in_turkey_ads)

def test_filter_by_skill(self):
response = self.client.get(reverse('equipmentpost-list'), {'min_skill_level': 2, 'max_skill_level': 4},
HTTP_AUTHORIZATION=f'JWT {self.token}')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'], self.skill_between_2_4)

7 changes: 7 additions & 0 deletions backend/equipmentposts/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.urls import path
from . import views
from rest_framework.routers import SimpleRouter

router = SimpleRouter()
router.register(r'equipments', views.EquipmentViewSet)
urlpatterns = router.urls
157 changes: 157 additions & 0 deletions backend/equipmentposts/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
from equipmentposts.models import EquipmentPost
from authentication.models import User
from equipmentposts.serializers import EquipmentSerializer
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
from django.http import JsonResponse
from rest_framework import status
from rest_framework.response import Response
from rest_framework import viewsets
from rest_framework import permissions
from rest_framework.decorators import action
from rest_framework.pagination import PageNumberPagination
from django.db.models import Q, F, Func, IntegerField
from datetime import datetime
# Create your views here.

class EquipmentPostsPagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 1000

class EquipmentViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
authentication_classes = [JWTAuthentication]
pagination_class = EquipmentPostsPagination
queryset = EquipmentPost.objects.all()
serializer_class = EquipmentSerializer
JWTauth = JWTAuthentication()
lookup_field = "id"

def wrap(self, request, data):
queryset = User.objects
owner = queryset.filter(id=data["owner"]).get().username
response = \
{
"@context": "https://www.w3.org/ns/activitystreams",
"summary": owner + " posted an equipment",
"type": "Create",
"actor":
{
"type": "Person",
"name": owner
},
"object":
{
"type": "Equipment",
"postId": data["id"],
"ownerId": data["owner"],
"content": data["content"],
"title": data["title"],
"creationDate": data["creation_date"],
"numberOfClicks": 0,
"location":
{
"name": data["location"],
"type": "Place",
"longitude": data["longitude"],
"latitude": data["latitude"],
"units": "m"
},
"url": data["url"],
"sport": data["sport"],
"equipmentMinSkillLevel": data[
"min_skill_level"],
"equipmentMaxSkillLevel": data[
"max_skill_level"],
"equipmentType": data["equipment_type"]
}
}

return response

def authenticate(self):
user, _ = self.JWTauth.authenticate(self.request)
return user.id == int(self.request.data["owner"])

def get_queryset(self):
queryset = EquipmentPost.objects.all()

# get all parameters for search
query = self.request.query_params.get('query')

location = self.request.query_params.get('location')


sport = self.request.query_params.get('sport')

owner_id = self.request.query_params.get('owner')

equipment_type = self.request.query_params.get('equipment_type')

min_latitude = self.request.query_params.get('min_latitude')
max_latitude = self.request.query_params.get('max_latitude')
min_longitude = self.request.query_params.get('min_longitude')
max_longitude = self.request.query_params.get('max_longitude')

min_skill = self.request.query_params.get('min_skill_level')
max_skill = self.request.query_params.get('max_skill_level')

min_creation_date = self.request.query_params.get('min_creation_date')
max_creation_date = self.request.query_params.get('max_creation_date')

# filter by query by searching in both title and description
if query is not None:
queryset = queryset.filter(Q(title__icontains=query) | Q(content__icontains=query))

# filter by name of the location
if location is not None:
queryset = queryset.filter(Q(location__icontains=location))

if equipment_type is not None:
queryset = queryset.filter(Q(equipment_type__icontains=equipment_type))

# filter by event date

# filter by sport category
if sport is not None:
queryset = queryset.filter(sport=sport)

# filter by owner of the event
if owner_id is not None:
queryset = queryset.filter(owner=User.objects.get(id=owner_id))

# filter by coordinates whether the locations are inside the rectangle
if min_latitude is not None and max_latitude is not None:
queryset = queryset.filter(Q(latitude__lte=max_latitude) & Q(latitude__gte=min_latitude))
if min_longitude is not None and max_longitude is not None:
queryset = queryset.filter(Q(longitude__lte=max_longitude) & Q(longitude__gte=min_longitude))

# filter by skill levels
if min_skill is not None:
queryset = queryset.filter(min_skill_level__gte=min_skill)
if max_skill is not None:
queryset = queryset.filter(max_skill_level__lte=max_skill)

# filter by creation date
if min_creation_date is not None:
queryset = queryset.filter(creation_date__gte=min_creation_date)
if max_creation_date is not None:
queryset = queryset.filter(creation_date__lte=max_creation_date)

return queryset

def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(self.wrap(request, serializer.data))

def create(self, request, *args, **kwargs):
if self.authenticate():
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(self.wrap(request, serializer.data), status=status.HTTP_201_CREATED, headers=headers)
else:
return JsonResponse(status=401, data={'detail': 'Unauthorized.'})
17 changes: 11 additions & 6 deletions backend/eventposts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
from django.contrib.postgres.fields import ArrayField
from datetime import datetime


def empty_list():
return list([])


class Post(models.Model):

owner = models.ForeignKey(User, on_delete=models.CASCADE)
Expand All @@ -15,6 +17,13 @@ class Post(models.Model):

creation_date = models.DateTimeField(default=datetime.now())
location = models.TextField(default="")
sport = models.CharField(max_length=30)

min_skill_level = models.IntegerField(default=0)
max_skill_level = models.IntegerField(default=0)

latitude = models.FloatField(default=1.0)
longitude = models.FloatField(default=1.0)

class Meta:
abstract = True
Expand All @@ -24,7 +33,6 @@ class EventPost(Post):
date = models.DateTimeField(default=datetime.now())
duration = models.IntegerField(default=60)

sport = models.CharField(max_length=30)
min_age = models.IntegerField(default=18)
max_age = models.IntegerField(default=75)

Expand All @@ -35,8 +43,5 @@ class EventPost(Post):
spec_applicants = ArrayField(models.IntegerField(), default=empty_list)
spectators = ArrayField(models.IntegerField(), default=empty_list)

min_skill_level = models.IntegerField(default=0)
max_skill_level = models.IntegerField(default=0)

latitude = models.FloatField(default=1.0)
longitude = models.FloatField(default=1.0)
class Meta:
app_label = 'eventposts'
Loading

0 comments on commit 1fab30e

Please sign in to comment.