diff --git a/projektBazy/api/__pycache__/__init__.cpython-312.pyc b/projektBazy/api/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..c077a90 Binary files /dev/null and b/projektBazy/api/__pycache__/__init__.cpython-312.pyc differ diff --git a/projektBazy/api/__pycache__/serializers.cpython-312.pyc b/projektBazy/api/__pycache__/serializers.cpython-312.pyc new file mode 100644 index 0000000..4d435e1 Binary files /dev/null and b/projektBazy/api/__pycache__/serializers.cpython-312.pyc differ diff --git a/projektBazy/api/__pycache__/urls.cpython-312.pyc b/projektBazy/api/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000..2b15086 Binary files /dev/null and b/projektBazy/api/__pycache__/urls.cpython-312.pyc differ diff --git a/projektBazy/api/__pycache__/views.cpython-312.pyc b/projektBazy/api/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000..9002851 Binary files /dev/null and b/projektBazy/api/__pycache__/views.cpython-312.pyc differ diff --git a/projektBazy/api/serializers.py b/projektBazy/api/serializers.py index 18057d7..a619846 100644 --- a/projektBazy/api/serializers.py +++ b/projektBazy/api/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from shop.models import Category, Listing, Address, Transaction +from shop.models import Category, Listing, Address, Transaction, Message, Chat, Review from django.contrib.auth.models import User class CategorySerializer(serializers.ModelSerializer): class Meta: @@ -54,3 +54,43 @@ def create(self, validated_data): ) return transaction +class ChatSerializer(serializers.ModelSerializer): + class Meta: + model = Chat + fields = ['buyer', 'seller', 'listing'] + read_only_fields = ['buyer', 'seller', 'listing'] + + def create(self, validated_data): + buyer = self.context['request'].user + seller = validated_data['seller'] + listing = validated_data['listing'] + + chat = Chat.objects.create( + buyer=buyer, + seller=seller, + listing=listing + ) + return chat + + +class MessageSerializer(serializers.ModelSerializer): + class Meta: + model = Message + fields = ['chat', 'sender', 'content', 'status', 'created_at', 'viewed_at'] + read_only_fields = ['chat', 'sender', 'status', 'created_at', 'viewed_at'] + + def create(self, validated_data): + chat = validated_data['chat'] + content = validated_data['content'] + sender = self.context['request'].user + message = Message.objects.create( + chat=chat, + content=content, + sender=sender + ) + return message + +class ReviewSerializer(serializers.ModelSerializer): + class Meta: + model = Review + fields = ['id', 'listing', 'reviewer', 'reviewee', 'rating', 'comment', 'created_at'] \ No newline at end of file diff --git a/projektBazy/api/urls.py b/projektBazy/api/urls.py index 3a1e96d..b074404 100644 --- a/projektBazy/api/urls.py +++ b/projektBazy/api/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .views import ListingsView, UserAddressesView +from .views import ListingsView, UserAddressesView, ReviewViewSet from . import views urlpatterns = [ path('',views.getData), @@ -18,11 +18,22 @@ path('listings/user//', ListingsView.as_view(), name='user-listings'), #/listings/category/2/ path('listings/category//', ListingsView.as_view(), name='category-listings'), + path('listings//', views.get_listing, name='listing'), #/user/1/addresses path('user//addresses/', UserAddressesView.as_view(), name='user-addresses'), path('transactions/', views.create_transaction, name='create-transaction'), path('transactions//update/', views.update_transaction_status, name='update-transaction-status'), - +#/addmessage/1/1 + path('addmessage//', views.add_chat_message, name='add-chat-message'), + path('addmessage/', views.add_chat_message, name='add-chat-message'), + path('chats/', views.get_chats, name='listing-chats'), + path('messages/', views.get_messages, name='chat-messages'), + path('viewedmessage/', views.set_message_viewed, name='message-viewed'), + #nwm + path('reviews/', ReviewViewSet.as_view({'get': 'list', 'post': 'create'}), name='review-list-create'), + path('reviews//', ReviewViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}), name='review-detail'), + path('reviews//user-reviews/', ReviewViewSet.as_view({'get': 'user_reviews'}), name='user-reviews'), + path('reviews//average-rating/', ReviewViewSet.as_view({'get': 'average_rating'}), name='average-rating'), ] #Do something like this to test it #http://127.0.0.1:8000/postAddress/ diff --git a/projektBazy/api/views.py b/projektBazy/api/views.py index cbac70c..57e5ef3 100644 --- a/projektBazy/api/views.py +++ b/projektBazy/api/views.py @@ -1,15 +1,18 @@ from rest_framework.response import Response from rest_framework.decorators import api_view, authentication_classes, permission_classes -from shop.models import Category, Listing, Address, Transaction +from shop.models import Category, Listing, Address, Transaction, Message, Chat, Review from rest_framework.authentication import SessionAuthentication, TokenAuthentication from rest_framework.permissions import IsAuthenticated -from .serializers import CategorySerializer, ListingSerializer, UserSerializer, AdressSerializer, TransactionSerializer +from .serializers import CategorySerializer, ListingSerializer, UserSerializer, AdressSerializer, TransactionSerializer, MessageSerializer, ChatSerializer, ReviewSerializer from rest_framework import status, generics from rest_framework.authtoken.models import Token from rest_framework.exceptions import NotFound from django.contrib.auth.models import User from django.shortcuts import get_object_or_404 - +from rest_framework import viewsets +from rest_framework.decorators import action +from django.db.models import Avg +from datetime import datetime #unnessesary @api_view(['GET']) def getCategory(request): @@ -208,7 +211,138 @@ def update_transaction_status(request, transaction_id): return Response(TransactionSerializer(transaction).data, status=status.HTTP_200_OK) +@api_view(['POST']) +@authentication_classes([SessionAuthentication, TokenAuthentication]) +@permission_classes([IsAuthenticated]) +def add_chat_message(request, chat_id, listing_id): + if request.method == 'POST': + try: + listing = Listing.objects.get(id=listing_id) + except Listing.DoesNotExist: + return Response({'detail': 'Listing not found'}, status=status.HTTP_404_NOT_FOUND) + + try: + chat = Chat.objects.get(id=chat_id) + if chat.listing_id != listing.id: + return Response({'detail': 'Invalid chat for the listing'}, status=status.HTTP_400_BAD_REQUEST) + + except Chat.DoesNotExist: + chat = None + + if chat is None: + + if request.user.id == listing.seller.id: + return Response({'detail': 'Seller can not start the chat'}, status=status.HTTP_400_BAD_REQUEST) + + try: + newChat = { + 'buyer': request.user, + 'seller': listing.seller, + 'listing': listing + } + chat_serializer = ChatSerializer(data=newChat, context={'request': request}) + if chat_serializer.is_valid(): + chat = chat_serializer.save(seller=listing.seller, buyer=request.user, listing=listing) + + except Exception as e: + return Response({'detail': 'Unable to create chat'}, status=status.HTTP_409_CONFLICT) + + new_message = { + 'content' : request.data.get('message') + } + + message_serializer = MessageSerializer(data=new_message, context={'request': request}) + + if message_serializer.is_valid(): + message_serializer.save(chat=chat) + return Response(message_serializer.data, status=status.HTTP_201_CREATED) + + return Response(message_serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +@api_view(['GET']) +def get_chats(request, listing_id): + try: + listing = Listing.objects.get(id=listing_id) + except Listing.DoesNotExist: + return Response({'detail': 'Listing not found'}, status=status.HTTP_404_NOT_FOUND) + chats = Chat.objects.filter(listing=listing_id) + serializer = ChatSerializer(chats, many=True) + return Response(serializer.data) +@api_view(['GET']) +def get_messages(request, chat_id): + try: + chat = Chat.objects.get(id=chat_id) + except Chat.DoesNotExist: + return Response({'detail': 'Chat not found'}, status=status.HTTP_404_NOT_FOUND) + messages = Message.objects.filter(chat=chat) + serializer = MessageSerializer(messages, many=True) + return Response(serializer.data) + +@api_view(['PATCH']) +def set_message_viewed(request, message_id): + try: + message = Message.objects.get(id=message_id) + message.status = Message.Viewed + message.viewed_at = datetime.now() + message.save() + except Message.DoesNotExist: + return Response({'detail': 'Message not found'}, status=status.HTTP_404_NOT_FOUND) + except Exception as e: + return Response({'detail': 'Unable to update'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + return Response(MessageSerializer(message).data) + +class ReviewViewSet(viewsets.ModelViewSet): + queryset = Review.objects.all() + serializer_class = ReviewSerializer + + def create(self, request, *args, **kwargs): + reviewer = request.user + listing_id = request.data.get('listing') + reviewee_id = request.data.get('reviewee') + + try: + listing = Listing.objects.get(id=listing_id) + if listing.seller == reviewer: + return Response({"error": "You can only review users for listings you purchased."}, status=status.HTTP_403_FORBIDDEN) + if listing.seller.id != int(reviewee_id): + return Response({"error": "Reviewee must be the seller of the purchased listing."}, status=status.HTTP_400_BAD_REQUEST) + except Listing.DoesNotExist: + return Response({"error": "Listing does not exist."}, status=status.HTTP_404_NOT_FOUND) + + return super().create(request, *args, **kwargs) + + def update(self, request, *args, **kwargs): + instance = self.get_object() + # if instance.reviewer != request.user: + # return Response({"error": "You can only update your own reviews."}, status=status.HTTP_403_FORBIDDEN) + return super().update(request, *args, **kwargs) + + @action(detail=True, methods=['get'], url_path='user-reviews') + def user_reviews(self, request, pk=None): + user = User.objects.get(pk=pk) + reviews = Review.objects.filter(reviewee=user) + serializer = self.get_serializer(reviews, many=True) + return Response(serializer.data) + + @action(detail=True, methods=['get'], url_path='average-rating') + def average_rating(self, request, pk=None): + user = User.objects.get(pk=pk) + average_rating = Review.objects.filter(reviewee=user).aggregate(Avg('rating'))['rating__avg'] + return Response({"average_rating": average_rating}) + + + + +@api_view(['GET']) +def get_listing(request, listing_id): + try: + listing = Listing.objects.get(id=listing_id) + serializer = ListingSerializer(listing) + return Response(serializer.data) + except Listing.DoesNotExist: + return Response({'detail': 'Listing not found'}, status=status.HTTP_404_NOT_FOUND) \ No newline at end of file diff --git a/projektBazy/db.sqlite3 b/projektBazy/db.sqlite3 new file mode 100644 index 0000000..71a5096 Binary files /dev/null and b/projektBazy/db.sqlite3 differ diff --git a/projektBazy/projektBazy/__pycache__/__init__.cpython-312.pyc b/projektBazy/projektBazy/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..43d3799 Binary files /dev/null and b/projektBazy/projektBazy/__pycache__/__init__.cpython-312.pyc differ diff --git a/projektBazy/projektBazy/__pycache__/settings.cpython-312.pyc b/projektBazy/projektBazy/__pycache__/settings.cpython-312.pyc new file mode 100644 index 0000000..0dbdda7 Binary files /dev/null and b/projektBazy/projektBazy/__pycache__/settings.cpython-312.pyc differ diff --git a/projektBazy/projektBazy/__pycache__/urls.cpython-312.pyc b/projektBazy/projektBazy/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000..c305e24 Binary files /dev/null and b/projektBazy/projektBazy/__pycache__/urls.cpython-312.pyc differ diff --git a/projektBazy/projektBazy/__pycache__/wsgi.cpython-312.pyc b/projektBazy/projektBazy/__pycache__/wsgi.cpython-312.pyc new file mode 100644 index 0000000..6a1081e Binary files /dev/null and b/projektBazy/projektBazy/__pycache__/wsgi.cpython-312.pyc differ diff --git a/projektBazy/projektBazy/settings.py b/projektBazy/projektBazy/settings.py index 8757f7d..0059f6a 100644 --- a/projektBazy/projektBazy/settings.py +++ b/projektBazy/projektBazy/settings.py @@ -83,16 +83,10 @@ DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.mysql', # Używamy MySQL jako backend (MariaDB jest kompatybilne) - 'NAME': 'shopDB', # Nazwa Twojej bazy danych - 'USER': '272561', # Użytkownik bazy danych - 'PASSWORD': 'osboxes.org', # Hasło do bazy danych - 'HOST': 'localhost', # Adres serwera bazy danych - 'PORT': '3306', # Domyślny port dla MariaDB/MySQL + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', } } - - # Password validation # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators diff --git a/projektBazy/shop/__pycache__/__init__.cpython-312.pyc b/projektBazy/shop/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..a062b08 Binary files /dev/null and b/projektBazy/shop/__pycache__/__init__.cpython-312.pyc differ diff --git a/projektBazy/shop/__pycache__/admin.cpython-312.pyc b/projektBazy/shop/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000..9cf982e Binary files /dev/null and b/projektBazy/shop/__pycache__/admin.cpython-312.pyc differ diff --git a/projektBazy/shop/__pycache__/apps.cpython-312.pyc b/projektBazy/shop/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000..c20793d Binary files /dev/null and b/projektBazy/shop/__pycache__/apps.cpython-312.pyc differ diff --git a/projektBazy/shop/__pycache__/models.cpython-312.pyc b/projektBazy/shop/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000..99a000a Binary files /dev/null and b/projektBazy/shop/__pycache__/models.cpython-312.pyc differ diff --git a/projektBazy/shop/admin.py b/projektBazy/shop/admin.py index 6d68aec..26bdf1b 100644 --- a/projektBazy/shop/admin.py +++ b/projektBazy/shop/admin.py @@ -1,8 +1,10 @@ from django.contrib import admin -from .models import User, Listing, Address, Category, Transaction +from .models import User, Listing, Address, Category, Transaction, Chat, Message # Register your models here. admin.site.register(Listing) admin.site.register(Address) admin.site.register(Category) -admin.site.register(Transaction) \ No newline at end of file +admin.site.register(Transaction) +admin.site.register(Chat) +admin.site.register(Message) \ No newline at end of file diff --git a/projektBazy/shop/migrations/0009_chat_review_message.py b/projektBazy/shop/migrations/0009_chat_review_message.py new file mode 100644 index 0000000..102b043 --- /dev/null +++ b/projektBazy/shop/migrations/0009_chat_review_message.py @@ -0,0 +1,52 @@ +# Generated by Django 4.2.17 on 2024-12-30 21:26 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('shop', '0008_alter_transaction_status'), + ] + + operations = [ + migrations.CreateModel( + name='Chat', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('buyer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chats_as_buyer', to=settings.AUTH_USER_MODEL)), + ('listing', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chats', to='shop.listing')), + ('seller', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chats_as_seller', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('buyer', 'seller', 'listing')}, + }, + ), + migrations.CreateModel( + name='Review', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('rating', models.IntegerField()), + ('comment', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('listing', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='shop.listing')), + ('reviewee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews_received', to=settings.AUTH_USER_MODEL)), + ('reviewer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews_given', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Message', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content', models.TextField()), + ('status', models.CharField(choices=[('sent', 'Sent'), ('Viewed', 'Viewed')], default='sent', max_length=6)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('viewed_at', models.DateTimeField(blank=True, default=None, null=True)), + ('chat', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='shop.chat')), + ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chats', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/projektBazy/shop/migrations/__pycache__/0001_initial.cpython-312.pyc b/projektBazy/shop/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000..0c990d6 Binary files /dev/null and b/projektBazy/shop/migrations/__pycache__/0001_initial.cpython-312.pyc differ diff --git a/projektBazy/shop/migrations/__pycache__/0002_rename_user_users_address.cpython-312.pyc b/projektBazy/shop/migrations/__pycache__/0002_rename_user_users_address.cpython-312.pyc new file mode 100644 index 0000000..8d663e6 Binary files /dev/null and b/projektBazy/shop/migrations/__pycache__/0002_rename_user_users_address.cpython-312.pyc differ diff --git a/projektBazy/shop/migrations/__pycache__/0003_alter_address_user_alter_listing_seller_delete_users.cpython-312.pyc b/projektBazy/shop/migrations/__pycache__/0003_alter_address_user_alter_listing_seller_delete_users.cpython-312.pyc new file mode 100644 index 0000000..7eb7068 Binary files /dev/null and b/projektBazy/shop/migrations/__pycache__/0003_alter_address_user_alter_listing_seller_delete_users.cpython-312.pyc differ diff --git a/projektBazy/shop/migrations/__pycache__/0004_category_listing_category.cpython-312.pyc b/projektBazy/shop/migrations/__pycache__/0004_category_listing_category.cpython-312.pyc new file mode 100644 index 0000000..fec2a39 Binary files /dev/null and b/projektBazy/shop/migrations/__pycache__/0004_category_listing_category.cpython-312.pyc differ diff --git a/projektBazy/shop/migrations/__pycache__/0005_remove_address_user_address_resident.cpython-312.pyc b/projektBazy/shop/migrations/__pycache__/0005_remove_address_user_address_resident.cpython-312.pyc new file mode 100644 index 0000000..fb295c3 Binary files /dev/null and b/projektBazy/shop/migrations/__pycache__/0005_remove_address_user_address_resident.cpython-312.pyc differ diff --git a/projektBazy/shop/migrations/__pycache__/0006_listing_created_at.cpython-312.pyc b/projektBazy/shop/migrations/__pycache__/0006_listing_created_at.cpython-312.pyc new file mode 100644 index 0000000..ae8d902 Binary files /dev/null and b/projektBazy/shop/migrations/__pycache__/0006_listing_created_at.cpython-312.pyc differ diff --git a/projektBazy/shop/migrations/__pycache__/0007_transaction.cpython-312.pyc b/projektBazy/shop/migrations/__pycache__/0007_transaction.cpython-312.pyc new file mode 100644 index 0000000..4c89302 Binary files /dev/null and b/projektBazy/shop/migrations/__pycache__/0007_transaction.cpython-312.pyc differ diff --git a/projektBazy/shop/migrations/__pycache__/0008_alter_transaction_status.cpython-312.pyc b/projektBazy/shop/migrations/__pycache__/0008_alter_transaction_status.cpython-312.pyc new file mode 100644 index 0000000..c84637e Binary files /dev/null and b/projektBazy/shop/migrations/__pycache__/0008_alter_transaction_status.cpython-312.pyc differ diff --git a/projektBazy/shop/migrations/__pycache__/0009_chat_review_message.cpython-312.pyc b/projektBazy/shop/migrations/__pycache__/0009_chat_review_message.cpython-312.pyc new file mode 100644 index 0000000..b00da86 Binary files /dev/null and b/projektBazy/shop/migrations/__pycache__/0009_chat_review_message.cpython-312.pyc differ diff --git a/projektBazy/shop/migrations/__pycache__/__init__.cpython-312.pyc b/projektBazy/shop/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..fa46c99 Binary files /dev/null and b/projektBazy/shop/migrations/__pycache__/__init__.cpython-312.pyc differ diff --git a/projektBazy/shop/models.py b/projektBazy/shop/models.py index 93ea9bb..fb70e39 100644 --- a/projektBazy/shop/models.py +++ b/projektBazy/shop/models.py @@ -63,4 +63,35 @@ def save(self, *args, **kwargs): def __str__(self): return f"Transaction between {self.seller.username} and {self.buyer.username} for {self.listing.title} curently {self.status}" - \ No newline at end of file +class Chat(models.Model): + buyer = models.ForeignKey(User, on_delete=models.CASCADE, related_name='chats_as_buyer', to_field='id') + seller = models.ForeignKey(User, on_delete=models.CASCADE, related_name='chats_as_seller', to_field='id') + listing = models.ForeignKey(Listing, on_delete=models.CASCADE, related_name='chats', to_field='id') + + class Meta: + unique_together=("buyer", "seller", "listing") + + +class Message(models.Model): + Sent = 'sent' + Viewed = 'Viewed' + + STATUS_CHOICES = [ + (Sent, 'Sent'), + (Viewed, 'Viewed'), + ] + + chat = models.ForeignKey(Chat, on_delete=models.CASCADE, related_name='messages', to_field='id') + sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name='chats', to_field='id') + content = models.TextField() + status = models.CharField(max_length=6, choices=STATUS_CHOICES, default=Sent) + created_at = models.DateTimeField(auto_now_add=True) + viewed_at = models.DateTimeField(default=None, blank=True, null=True) + +class Review(models.Model): + listing = models.ForeignKey(Listing, on_delete=models.CASCADE) + reviewer = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reviews_given') + reviewee = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reviews_received') + rating = models.IntegerField() + comment = models.TextField() + created_at = models.DateTimeField(auto_now_add=True) \ No newline at end of file