Skip to content

Commit

Permalink
ZDL-47: Add categories model API endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
RyanAquino committed Aug 9, 2022
1 parent 6223dc9 commit e2b8b0f
Show file tree
Hide file tree
Showing 21 changed files with 266 additions and 3 deletions.
7 changes: 7 additions & 0 deletions authentication/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
from rest_framework import permissions


class AdminAccessPermission(permissions.BasePermission):
group = "Admin"

def has_permission(self, request, view):
return request.method in permissions.SAFE_METHODS or request.user.is_superuser


class CustomerAccessPermission(permissions.BasePermission):
group = "Customers"

Expand Down
1 change: 1 addition & 0 deletions authentication/tests/factories/user_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Meta:
last_name = "account"
password = PostGenerationMethodCall("set_password", "password")
is_active = True
is_superuser = False
auth_provider = AuthProviders.email.value
date_joined = FuzzyNaiveDateTime(datetime(2022, 1, 1))
last_login = FuzzyNaiveDateTime(datetime(2022, 1, 1))
Expand Down
2 changes: 1 addition & 1 deletion authentication/tests/test_auth_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


@pytest.mark.django_db
def test_create_superuser_command(admin_group):
def test_create_superuser_command():
"""
Test admin account creation
"""
Expand Down
Empty file added categories/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions categories/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.contrib import admin

from categories.models import Category

admin.site.register(Category)
6 changes: 6 additions & 0 deletions categories/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class CategoryConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "categories"
28 changes: 28 additions & 0 deletions categories/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 3.2.12 on 2022-08-08 16:29

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = []

operations = [
migrations.CreateModel(
name="Category",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.TextField(unique=True)),
],
),
]
Empty file.
5 changes: 5 additions & 0 deletions categories/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.db import models


class Category(models.Model):
name = models.TextField(unique=True)
9 changes: 9 additions & 0 deletions categories/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from rest_framework import serializers

from categories.models import Category


class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = "__all__"
Empty file added categories/tests/__init__.py
Empty file.
Empty file.
11 changes: 11 additions & 0 deletions categories/tests/factories/category_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import factory
from factory.django import DjangoModelFactory

from categories.models import Category


class CategoryFactory(DjangoModelFactory):
class Meta:
model = Category

name = factory.Sequence(lambda n: f"Category {n}")
131 changes: 131 additions & 0 deletions categories/tests/test_categories_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import pytest
from django.test.client import MULTIPART_CONTENT

from categories.models import Category
from categories.tests.factories.category_factory import CategoryFactory


@pytest.mark.django_db
def test_list_all_categories(logged_in_client):
"""
Test list all products categories with empty database
"""
response = logged_in_client.get("/v1/categories/")

assert response.status_code == 200, response.json()
assert len(response.json()["results"]) == 0
assert response.json()["results"] == []


@pytest.mark.django_db
def test_list_all_product_categories(logged_in_client):
"""
Test list all product categories
"""
CategoryFactory()
response = logged_in_client.get("/v1/categories/")

response_data = response.json()["results"]

assert response.status_code == 200, response_data
assert len(response_data) == 1


@pytest.mark.django_db
def test_create_product_category(admin_client):
"""
Test admin create product category
"""
category_data = {"name": "test-category-create"}
response = admin_client.post("/v1/categories/", category_data, format="json")
assert response.status_code == 201 and response.json()
assert Category.objects.count() == 1


@pytest.mark.django_db
def test_retrieve_product_category(logged_in_client):
"""
Test retrieve specific product category
"""
category = CategoryFactory()
response = logged_in_client.get(f"/v1/categories/{category.id}/")

response_data = response.json()

assert response.status_code == 200 and response_data
assert response_data["id"] == category.id


@pytest.mark.django_db
def test_update_product_category(admin_client):
"""
Test update a single product category
"""
content_type = MULTIPART_CONTENT
category = CategoryFactory()
data = {
"name": "Category 1 edited",
}
data = admin_client._encode_json({} if not data else data, content_type)
encoded_data = admin_client._encode_data(data, content_type)
response = admin_client.generic(
"PUT",
f"/v1/categories/{category.id}/",
encoded_data,
content_type=content_type,
secure=False,
enctype="multipart/form-data",
)

response_data = response.json()
assert response.status_code == 200, response.data
assert data["name"] == response_data["name"]


@pytest.mark.django_db
def test_patch_product_category(admin_client):
"""
Test patch a single product property
"""
content_type = MULTIPART_CONTENT
category = CategoryFactory()
data = {
"name": "Category 1 patch edited",
}
data = admin_client._encode_json({} if not data else data, content_type)
encoded_data = admin_client._encode_data(data, content_type)
response = admin_client.generic(
"PATCH",
f"/v1/categories/{category.id}/",
encoded_data,
content_type=content_type,
secure=False,
enctype="multipart/form-data",
)

response_data = response.json()
assert response.status_code == 200, response.data
assert data["name"] == response_data["name"]


@pytest.mark.django_db
def test_delete_product_category(admin_client):
"""
Test delete specific product category
"""
category = CategoryFactory()
response = admin_client.delete(f"/v1/categories/{category.id}/")
assert response.status_code == 204
assert Category.objects.count() == 0


@pytest.mark.django_db
def test_non_safe_permission_product_category_should_raise_403(logged_in_client):
"""
Test non safe method permission access on product category
"""
category = CategoryFactory()
response = logged_in_client.delete(f"/v1/categories/{category.id}/")
assert response.status_code == 403 and response.json() == {
"detail": "You do not have permission to perform this action."
}
8 changes: 8 additions & 0 deletions categories/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.urls import include, path
from rest_framework import routers

from categories.views import CategoryViewSet

router = routers.DefaultRouter()
router.register("", CategoryViewSet)
urlpatterns = [path("", include(router.urls))]
11 changes: 11 additions & 0 deletions categories/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from rest_framework.viewsets import ModelViewSet

from authentication.permissions import AdminAccessPermission
from categories.models import Category
from categories.serializers import CategorySerializer


class CategoryViewSet(ModelViewSet):
permission_classes = [AdminAccessPermission]
queryset = Category.objects.all().order_by("id")
serializer_class = CategorySerializer
13 changes: 11 additions & 2 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,14 @@ def logged_in_user():


@pytest.fixture
def admin_group():
UserFactory.create(groups=(Group.objects.filter(name="Admins")))
def admin_client(admin_user):
user_token = admin_user.tokens().token
return Client(HTTP_AUTHORIZATION=f"Bearer {user_token}")


@pytest.fixture
def admin_user():
skip_if_no_django()
return UserFactory.create(
groups=(Group.objects.filter(name="Admins")), is_superuser=True
)
25 changes: 25 additions & 0 deletions products/migrations/0006_product_category.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 3.2.12 on 2022-08-08 16:29

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("categories", "0001_initial"),
("products", "0005_alter_product_price"),
]

operations = [
migrations.AddField(
model_name="product",
name="category",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="categories.category",
),
),
]
5 changes: 5 additions & 0 deletions products/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from django.db import models
from django.db.models.functions import Upper

from categories.models import Category


class Product(models.Model):
supplier = models.ForeignKey(
Expand All @@ -16,6 +18,9 @@ class Product(models.Model):
image = models.ImageField(null=True)
quantity = models.IntegerField(default=1)
created_at = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey(
Category, on_delete=models.SET_NULL, null=True, blank=True
)

REQUIRED_FIELDS = "__all__"

Expand Down
1 change: 1 addition & 0 deletions zadalaAPI/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"corsheaders",
"drf_yasg",
"products",
"categories",
"orders",
"authentication",
"rest_framework",
Expand Down
1 change: 1 addition & 0 deletions zadalaAPI/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
urlpatterns = [
path("admin/", admin.site.urls),
path("v1/products/", include("products.urls")),
path("v1/categories/", include("categories.urls")),
path("v1/orders/", include("orders.urls")),
path("v1/auth/", include("authentication.urls")),
path("v1/social-auth/", include("social_auth.urls")),
Expand Down

0 comments on commit e2b8b0f

Please sign in to comment.