mirror of
https://github.com/more-tech4-magnum-opus/backend.git
synced 2024-11-21 19:16:33 +03:00
added marketplace endpoints, permissions
This commit is contained in:
parent
50a6c9d39c
commit
4e4735d7e2
0
app/common/__init__.py
Normal file
0
app/common/__init__.py
Normal file
23
app/common/permissions.py
Normal file
23
app/common/permissions.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
from rest_framework import permissions
|
||||||
|
|
||||||
|
|
||||||
|
class IsManager(permissions.BasePermission):
|
||||||
|
"""
|
||||||
|
Checks if request user is an admin or hr
|
||||||
|
"""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
if request.method in permissions.SAFE_METHODS:
|
||||||
|
return True
|
||||||
|
return request.user.is_manager
|
||||||
|
|
||||||
|
|
||||||
|
class IsAdmin(permissions.BasePermission):
|
||||||
|
"""
|
||||||
|
Checks if request user is an admin or hr
|
||||||
|
"""
|
||||||
|
|
||||||
|
def has_permission(self, request, view):
|
||||||
|
if request.method in permissions.SAFE_METHODS:
|
||||||
|
return True
|
||||||
|
return request.user.is_admin
|
|
@ -1,6 +1,8 @@
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
|
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
|
||||||
|
|
||||||
|
from marketplace.api.views import ListCreateProductApi, RetireUpdateDestroyProductApi
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(
|
path(
|
||||||
"auth/",
|
"auth/",
|
||||||
|
@ -12,9 +14,19 @@ urlpatterns = [
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"user/",
|
"marketplace/",
|
||||||
include(
|
include(
|
||||||
[
|
[
|
||||||
|
path(
|
||||||
|
"product/",
|
||||||
|
ListCreateProductApi.as_view(),
|
||||||
|
name="list_create_product",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"product/<str:slug>",
|
||||||
|
RetireUpdateDestroyProductApi.as_view(),
|
||||||
|
name="get_update_destroy_product",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -62,7 +62,7 @@ DJANGO_APPS = [
|
||||||
]
|
]
|
||||||
THIRD_PARTY_APPS = ["rest_framework", "corsheaders", "django_celery_beat", "drf_yasg"]
|
THIRD_PARTY_APPS = ["rest_framework", "corsheaders", "django_celery_beat", "drf_yasg"]
|
||||||
|
|
||||||
LOCAL_APPS = ["users"]
|
LOCAL_APPS = ["users", "marketplace", "events"]
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||||
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
||||||
|
|
||||||
|
|
0
app/marketplace/api/__init__.py
Normal file
0
app/marketplace/api/__init__.py
Normal file
30
app/marketplace/api/serializers.py
Normal file
30
app/marketplace/api/serializers.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from marketplace.models import Product
|
||||||
|
|
||||||
|
|
||||||
|
class ProductSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Product
|
||||||
|
fields = [
|
||||||
|
"name",
|
||||||
|
"slug",
|
||||||
|
"description",
|
||||||
|
"image",
|
||||||
|
"image_cropped",
|
||||||
|
"nft",
|
||||||
|
"price",
|
||||||
|
"creator",
|
||||||
|
]
|
||||||
|
extra_kwargs = {
|
||||||
|
"image": {"write_only": True},
|
||||||
|
"nft": {"read_only": True},
|
||||||
|
"slug": {"read_only": True},
|
||||||
|
"image_cropped": {"read_only": True},
|
||||||
|
"creator": {"read_only": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
return Product.objects.create(
|
||||||
|
**validated_data, creator=self.context["request"].user
|
||||||
|
)
|
29
app/marketplace/api/views.py
Normal file
29
app/marketplace/api/views.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
from rest_framework import generics
|
||||||
|
from rest_framework.generics import get_object_or_404
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from rest_framework.parsers import FormParser, MultiPartParser
|
||||||
|
|
||||||
|
from marketplace.api.serializers import ProductSerializer
|
||||||
|
from marketplace.models import Product
|
||||||
|
from common.permissions import IsManager
|
||||||
|
|
||||||
|
|
||||||
|
class ListCreateProductApi(generics.ListCreateAPIView):
|
||||||
|
serializer_class = ProductSerializer
|
||||||
|
parser_classes = [FormParser, MultiPartParser]
|
||||||
|
permission_classes = [IsAuthenticated, IsManager]
|
||||||
|
queryset = Product.objects.all()
|
||||||
|
|
||||||
|
|
||||||
|
class RetireUpdateDestroyProductApi(generics.RetrieveUpdateDestroyAPIView):
|
||||||
|
serializer_class = ProductSerializer
|
||||||
|
parser_classes = [FormParser, MultiPartParser]
|
||||||
|
permission_classes = [IsAuthenticated, IsManager]
|
||||||
|
queryset = Product.objects.all()
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
block = get_object_or_404(
|
||||||
|
Product,
|
||||||
|
slug=self.request.parser_context["kwargs"]["slug"],
|
||||||
|
)
|
||||||
|
return block
|
|
@ -4,3 +4,6 @@ from django.apps import AppConfig
|
||||||
class MarketplaceConfig(AppConfig):
|
class MarketplaceConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'marketplace'
|
name = 'marketplace'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import marketplace.signals
|
||||||
|
|
30
app/marketplace/migrations/0001_initial.py
Normal file
30
app/marketplace/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Generated by Django 4.0.8 on 2022-10-08 06:31
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Product',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=200)),
|
||||||
|
('description', models.TextField()),
|
||||||
|
('image', models.ImageField(upload_to='uploads/')),
|
||||||
|
('image_cropped', models.ImageField(blank=True, upload_to='cropped/')),
|
||||||
|
('nft', models.CharField(blank=True, max_length=500)),
|
||||||
|
('price', models.IntegerField()),
|
||||||
|
('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,3 +1,19 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
# Create your models here.
|
from users.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class Product(models.Model):
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
slug = models.SlugField(max_length=20)
|
||||||
|
description = models.TextField(blank=True)
|
||||||
|
|
||||||
|
image = models.ImageField(upload_to="uploads/")
|
||||||
|
image_cropped = models.ImageField(upload_to="cropped/", blank=True)
|
||||||
|
nft = models.CharField(max_length=500, blank=True)
|
||||||
|
|
||||||
|
price = models.IntegerField()
|
||||||
|
creator = models.ForeignKey(User, related_name="products", on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
25
app/marketplace/signals.py
Normal file
25
app/marketplace/signals.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
from django.core.files import File
|
||||||
|
from django.db.models.signals import pre_save, post_save
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from utils.file import crop_image
|
||||||
|
from utils.generators import generate_charset
|
||||||
|
from .models import Product
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_save, sender=Product)
|
||||||
|
def create_product(sender, instance, **kwargs):
|
||||||
|
slug = generate_charset(5)
|
||||||
|
while Product.objects.filter(slug=slug).exists():
|
||||||
|
slug = generate_charset(5)
|
||||||
|
instance.slug = slug
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=Product)
|
||||||
|
def process_product(sender, instance, created, **kwargs):
|
||||||
|
if instance.image and kwargs["update_fields"] != frozenset({"image_cropped"}):
|
||||||
|
instance.image_cropped = File(
|
||||||
|
crop_image(instance.image.path, cut_to=(250, 250)),
|
||||||
|
name=instance.image.path.split(".")[0].split("/")[-1] + ".png",
|
||||||
|
)
|
||||||
|
instance.save(update_fields=["image_cropped"])
|
|
@ -1,3 +0,0 @@
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
# Create your views here.
|
|
|
@ -4,7 +4,7 @@ Django==4.0.8
|
||||||
django-cors-headers==3.13.0
|
django-cors-headers==3.13.0
|
||||||
django-environ==0.9.0
|
django-environ==0.9.0
|
||||||
"drf-yasg[validation]"
|
"drf-yasg[validation]"
|
||||||
|
Pillow==9.2.0
|
||||||
|
|
||||||
redis==4.3.4
|
redis==4.3.4
|
||||||
psycopg2-binary==2.9.4
|
psycopg2-binary==2.9.4
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
# Generated by Django 4.0.8 on 2022-10-07 21:20
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('users', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='salary',
|
|
||||||
field=models.IntegerField(default=0),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('WORKER', 'worker'), ('HR', 'human resources'), ('ADMIN', 'administrator')], default='WORKER', max_length=6),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -28,8 +28,12 @@ class User(AbstractUser):
|
||||||
super(AbstractUser, self).save(*args, **kwargs)
|
super(AbstractUser, self).save(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def can_create_events(self):
|
def is_manager(self):
|
||||||
return self.type in [self.WorkerType.HR, self.WorkerType.ADMIN]
|
return self.type in [self.WorkerType.HR, self.WorkerType.ADMIN]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_admin(self):
|
||||||
|
return self.type == self.WorkerType.ADMIN
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["-id"]
|
ordering = ["-id"]
|
||||||
|
|
0
app/utils/__init__.py
Normal file
0
app/utils/__init__.py
Normal file
16
app/utils/file.py
Normal file
16
app/utils/file.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
from PIL import Image
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
|
def crop_image(image_path: str, cut_to=(500, 500)):
|
||||||
|
"""Makes image's thumbnail bt given parameters. By default, crops to 500x500"""
|
||||||
|
img = Image.open(image_path)
|
||||||
|
blob = BytesIO()
|
||||||
|
|
||||||
|
try:
|
||||||
|
img.thumbnail(cut_to, Image.ANTIALIAS)
|
||||||
|
except IOError:
|
||||||
|
print("Can't crop")
|
||||||
|
|
||||||
|
img.save(blob, "PNG")
|
||||||
|
return blob
|
8
app/utils/generators.py
Normal file
8
app/utils/generators.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import string
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
|
||||||
|
def generate_charset(length: int):
|
||||||
|
return "".join(
|
||||||
|
secrets.choice(string.digits + string.ascii_letters) for _ in range(length)
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user