Skip to content

Commit

Permalink
API endpoints and dynamic responses (#9)
Browse files Browse the repository at this point in the history
* add api endpoints

* enable basic auth

* change event to Observation

* lint and set tests
  • Loading branch information
submarcos authored Jul 1, 2024
1 parent e64fb6b commit 5022839
Show file tree
Hide file tree
Showing 34 changed files with 498 additions and 311 deletions.
10 changes: 5 additions & 5 deletions backend/dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ click==8.1.7
# via
# black
# pip-tools
coverage==7.5.3
coverage==7.5.4
# via -r dev-requirements.in
curtsies==0.4.2
# via bpython
Expand All @@ -42,9 +42,9 @@ django-debug-toolbar==4.4.2
# via -r dev-requirements.in
factory-boy==3.3.0
# via -r dev-requirements.in
faker==25.8.0
faker==26.0.0
# via factory-boy
flake8==7.0.0
flake8==7.1.0
# via -r dev-requirements.in
greenlet==3.0.3
# via bpython
Expand All @@ -67,7 +67,7 @@ pip-tools==7.4.1
# via -r dev-requirements.in
platformdirs==4.2.2
# via black
pycodestyle==2.11.1
pycodestyle==2.12.0
# via flake8
pyflakes==3.2.0
# via flake8
Expand All @@ -94,7 +94,7 @@ sqlparse==0.5.0
# django-debug-toolbar
tblib==3.0.0
# via -r dev-requirements.in
urllib3==2.2.1
urllib3==2.2.2
# via
# -c requirements.txt
# requests
Expand Down
6 changes: 4 additions & 2 deletions backend/project/accounts/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ class UserAdmin(BaseUserAdmin):
"first_name",
"last_name",
"is_active",
"is_staff",
"is_superuser",
"date_joined",
"last_login",
)
list_filter = (
"is_superuser",
"is_active",
)
search_fields = ("email", "first_name", "last_name")
ordering = ("email",)
fieldsets = (
Expand All @@ -37,7 +40,6 @@ class UserAdmin(BaseUserAdmin):
{
"fields": (
"is_active",
"is_staff",
"is_superuser",
"groups",
"user_permissions",
Expand Down
1 change: 0 additions & 1 deletion backend/project/accounts/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ def create_user(self, email, password=None, **extra_fields):
return user

def create_superuser(self, email, password=None, **extra_fields):
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)

return self.create_user(email, password, **extra_fields)
10 changes: 1 addition & 9 deletions backend/project/accounts/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 5.0.6 on 2024-06-12 20:15
# Generated by Django 5.0.6 on 2024-06-28 13:23

import django.db.models.functions.datetime
import django.utils.timezone
Expand Down Expand Up @@ -72,14 +72,6 @@ class Migration(migrations.Migration):
verbose_name="date joined",
),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
(
"is_active",
models.BooleanField(
Expand Down
9 changes: 4 additions & 5 deletions backend/project/accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ class User(AbstractBaseUser, PermissionsMixin):
date_joined = models.DateTimeField(
_("date joined"), default=timezone.now, db_default=Now()
)
is_staff = models.BooleanField(
_("staff status"),
default=False,
help_text=_("Designates whether the user can log into this admin site."),
)
is_active = models.BooleanField(
_("active"),
default=True,
Expand All @@ -37,6 +32,10 @@ class User(AbstractBaseUser, PermissionsMixin):
EMAIL_FIELD = "email"
USERNAME_FIELD = "email"

@property
def is_staff(self):
return self.is_superuser

class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
Expand Down
File renamed without changes.
17 changes: 17 additions & 0 deletions backend/project/accounts/tests/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import factory
from factory import faker

from ..models import User


class UserFactory(factory.django.DjangoModelFactory):
email = faker.Faker("email")
password = factory.PostGenerationMethodCall("set_password", "password")
is_active = True

class Meta:
model = User


class SuperUserFactory(UserFactory):
is_superuser = True
16 changes: 16 additions & 0 deletions backend/project/accounts/tests/test_admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.test import TestCase

from .factories import SuperUserFactory, UserFactory


class AccountAdminTestCase(TestCase):
def test_superuser_ca(self):
"""Test that only superuser can log in to the admin site."""
simple_user = UserFactory()
self.client.force_login(simple_user)
response = self.client.get("/admin/")
self.assertEqual(response.status_code, 302)
super_user = SuperUserFactory()
self.client.force_login(super_user)
response = self.client.get("/admin/")
self.assertEqual(response.status_code, 200)
14 changes: 14 additions & 0 deletions backend/project/accounts/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from django.test import TestCase

from .factories import SuperUserFactory, UserFactory


class AccountTestCase(TestCase):
def test_is_staff(self):
"""Test that is_staff is same value as is_superuser"""
user = UserFactory()
self.assertFalse(user.is_staff)
self.assertFalse(user.is_superuser)
super_user = SuperUserFactory()
self.assertTrue(super_user.is_staff)
self.assertTrue(super_user.is_superuser)
71 changes: 0 additions & 71 deletions backend/project/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,71 +0,0 @@
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import CreateModelMixin
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.viewsets import GenericViewSet

from project.accounts.models import User
from project.api.filters import EventFilterSet
from project.api.serializers import (
AccountSerializer,
EventDetailSerializer,
EventListSerializer,
EventTypeSerializer,
SettingsSerializer,
)
from project.events.models import Event, EventType


class SettingsApiView(GenericAPIView):
serializer_class = SettingsSerializer

def get(self, request):
data = {}
event_types = EventType.objects.prefetch_related("sub_types").all()
event_types_serialized = EventTypeSerializer(
event_types, many=True, context={"request": request}
)
data["event_types"] = event_types_serialized.data
data["event_url"] = reverse("api:events-list", request=request)
return Response(data)


class EventViewSet(viewsets.ModelViewSet):
filter_backends = (DjangoFilterBackend,)
filterset_class = EventFilterSet

def get_serializer_class(self):
if self.action == "list":
return EventListSerializer
return EventDetailSerializer

def get_queryset(self):
qs = Event.objects.all().select_related("source", "event_subtype__event_type")
if self.action not in ["list", "retrieve"] or self.request.user.is_anonymous:
# list and retrieve are public
if self.request.user.is_anonymous:
# anonymous can't change anything
qs = qs.none()
elif not self.request.user.is_staff and not self.request.user.is_superuser:
# nor anonymous, nor admin
qs = self.request.user.events.all()
return qs

def perform_create(self, serializer):
observer = self.request.user if self.request.user.is_authenticated else None
serializer.save(observer=observer)


class AccountViewSet(CreateModelMixin, GenericViewSet):
queryset = User.objects.all()
serializer_class = AccountSerializer

@action(detail=False, methods=["get"])
def mine(self, request, *args, **kwargs):
if self.request.user.is_anonymous:
return Response({}, status=status.HTTP_404_NOT_FOUND)
serializer = self.get_serializer(request.user)
return Response(serializer.data)
8 changes: 8 additions & 0 deletions backend/project/api/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _


class ApiConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "project.api"
verbose_name = _("API")
16 changes: 11 additions & 5 deletions backend/project/api/filters.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
from django_filters.fields import DateRangeField
from django_filters.rest_framework import FilterSet
from django_filters.rest_framework import FilterSet, filters

from project.events.models import Event
from project.observations.models import Observation


class EventFilterSet(FilterSet):
class ObservationFilterSet(FilterSet):
event_date = DateRangeField()
fields = filters.CharFilter(
method="filter_fields", help_text="filter fields you want to get"
)

def filter_fields(self, qs):
return qs

class Meta:
model = Event
fields = ["event_date", "event_subtype"]
model = Observation
fields = ["event_date", "observation_subtype", "fields"]
120 changes: 0 additions & 120 deletions backend/project/api/serializers.py

This file was deleted.

File renamed without changes.
Loading

0 comments on commit 5022839

Please sign in to comment.