diff --git a/.gitignore b/.gitignore index a168bd0..eb2fd5f 100755 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,4 @@ static/ staticfiles/ # Ignore logs -*.log \ No newline at end of file +*.log diff --git a/plant_catalog/catalog/admin.py b/plant_catalog/catalog/admin.py index 2696baa..750901c 100755 --- a/plant_catalog/catalog/admin.py +++ b/plant_catalog/catalog/admin.py @@ -1,7 +1,33 @@ from django.contrib import admin -from .models import Plant +from .models import Plant, PlantImage + +# Inline class for handling images +class PlantImageInline(admin.TabularInline): + model = PlantImage + extra = 3 # Display three empty forms for adding new images + fields = ('image',) # Only include fields that exist on PlantImage + + can_delete = True # Allow deletion of images + max_num = 10 # Limit to 10 images per plant (optional) @admin.register(Plant) class PlantAdmin(admin.ModelAdmin): - list_display = ('name', 'image') - search_fields = ('name',) + list_display = ('id', 'name', 'description', 'get_images', 'buy_price', 'sell_price', 'stock') + search_fields = ('name','description') # Add search functionality + inlines = [PlantImageInline] # Add inline editing for images + + def save_related(self, request, form, formsets, change): + super().save_related(request, form, formsets, change) + # Ensure all related PlantImage objects are saved correctly + for formset in formsets: + if hasattr(formset, 'save'): + formset.save() + + def get_images(self, obj): + """Display the first image or a placeholder in the list view.""" + images = obj.images.all() # Access related images via the 'images' related_name + if images.exists(): + return images[0].image.url # Show URL of the first image + return "No Images" + + get_images.short_description = "Images" # Header for the admin column diff --git a/plant_catalog/catalog/api_views.py b/plant_catalog/catalog/api_views.py index 44a9863..556ea27 100755 --- a/plant_catalog/catalog/api_views.py +++ b/plant_catalog/catalog/api_views.py @@ -3,9 +3,14 @@ from rest_framework import generics from rest_framework.parsers import MultiPartParser, FormParser, JSONParser from rest_framework.filters import SearchFilter +from rest_framework.response import Response +from rest_framework import status + +from rest_framework.views import APIView +from django.shortcuts import get_object_or_404 from .permissions import IsAuthenticatedOrReadOnly -from .models import Plant +from .models import Plant, PlantImage from .serializers import PlantSerializer # List and Create Plants @@ -22,4 +27,14 @@ class PlantRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView): queryset = Plant.objects.all() serializer_class = PlantSerializer parser_classes = [JSONParser, MultiPartParser, FormParser] # Allow file uploads and form data - permission_classes = [IsAuthenticatedOrReadOnly] # Apply custom permission \ No newline at end of file + permission_classes = [IsAuthenticatedOrReadOnly] # Apply custom permission + + +class PlantImageDeleteAPIView(APIView): + permission_classes = [IsAuthenticatedOrReadOnly] # Ensure only authenticated users can delete + + def delete(self, request, image_id): + image = get_object_or_404(PlantImage, id=image_id) + image.delete() # Delete the image object from the database + return Response({"message": "Image deleted successfully"}, status=status.HTTP_204_NO_CONTENT) + diff --git a/plant_catalog/catalog/models.py b/plant_catalog/catalog/models.py index bc757ea..17af994 100755 --- a/plant_catalog/catalog/models.py +++ b/plant_catalog/catalog/models.py @@ -1,14 +1,12 @@ -# catalog/models.py - from django.db import models class Plant(models.Model): name = models.CharField(max_length=100) - description = models.TextField(null=True, blank=True) # Allow null and blank - image = models.ImageField(upload_to='plants/', null=True, blank=True) # Optional image - buy_price = models.FloatField(null=True, blank=True) # Allow null for buy price - sell_price = models.FloatField(null=True, blank=True) # Allow null for sell price - stock = models.IntegerField(default=0) # Default to 0 + description = models.TextField(null=True, blank=True) + buy_price = models.FloatField(null=True, blank=True) + sell_price = models.FloatField(null=True, blank=True) + stock = models.IntegerField(default=0) - def __str__(self): - return self.name +class PlantImage(models.Model): + plant = models.ForeignKey(Plant, related_name='images', on_delete=models.CASCADE) + image = models.ImageField(upload_to='plants/') diff --git a/plant_catalog/catalog/permissions.py b/plant_catalog/catalog/permissions.py old mode 100644 new mode 100755 diff --git a/plant_catalog/catalog/serializers.py b/plant_catalog/catalog/serializers.py index 20dfb76..2f4558d 100755 --- a/plant_catalog/catalog/serializers.py +++ b/plant_catalog/catalog/serializers.py @@ -1,17 +1,41 @@ -# catalog/serializers.py - from rest_framework import serializers -from .models import Plant +from .models import Plant, PlantImage + +class PlantImageSerializer(serializers.ModelSerializer): + class Meta: + model = PlantImage + fields = ['id', 'image'] # Fields to expose for each image + class PlantSerializer(serializers.ModelSerializer): + images = PlantImageSerializer(many=True, read_only=True) # Read existing images + uploaded_images = serializers.ListField(child=serializers.ImageField(), write_only=True, required=False) # Accept new images + class Meta: model = Plant - fields = ['id', 'name', 'description', 'image', 'buy_price', 'sell_price', 'stock'] # Include all fields - extra_kwargs = { - 'buy_price': {'required': False, 'allow_null': True}, # Allow null for buy price - 'sell_price': {'required': False, 'allow_null': True}, # Allow null for sell price - 'image': {'required': False, 'allow_null': True}, # Allow image to be optional - 'description': {'required': False, 'allow_null': True}, # Optional description - } + fields = ['id', 'name', 'description', 'buy_price', 'sell_price', 'stock', 'images', 'uploaded_images'] # Include uploaded_images + + def create(self, validated_data): + uploaded_images = validated_data.pop('uploaded_images', []) # Get images from request + plant = Plant.objects.create(**validated_data) + + for image in uploaded_images: + PlantImage.objects.create(plant=plant, image=image) # Save each image + + return plant + + def update(self, instance, validated_data): + uploaded_images = validated_data.pop('uploaded_images', []) + + instance.name = validated_data.get('name', instance.name) + instance.description = validated_data.get('description', instance.description) + instance.buy_price = validated_data.get('buy_price', instance.buy_price) + instance.sell_price = validated_data.get('sell_price', instance.sell_price) + instance.stock = validated_data.get('stock', instance.stock) + instance.save() + + for image in uploaded_images: + PlantImage.objects.create(plant=instance, image=image) # Save new images + + return instance - \ No newline at end of file diff --git a/plant_catalog/catalog/urls.py b/plant_catalog/catalog/urls.py index 3376bb2..154dd55 100755 --- a/plant_catalog/catalog/urls.py +++ b/plant_catalog/catalog/urls.py @@ -3,7 +3,7 @@ from django.http import HttpResponseRedirect from django.urls import path from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView -from .api_views import PlantListCreateAPIView, PlantRetrieveUpdateDestroyAPIView +from .api_views import PlantListCreateAPIView, PlantRetrieveUpdateDestroyAPIView, PlantImageDeleteAPIView urlpatterns = [ path('', lambda request: HttpResponseRedirect('/api/plants/')), @@ -11,6 +11,7 @@ urlpatterns = [ path('api/plants//', PlantRetrieveUpdateDestroyAPIView.as_view(), name='plant_detail_update_delete'), # Retrieve, Update, Delete path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path('api/plant-images//', PlantImageDeleteAPIView.as_view(), name='plant_image_delete'), ] diff --git a/plant_catalog/plant_catalog/settings.py b/plant_catalog/plant_catalog/settings.py index 6666d86..79f8ea0 100755 --- a/plant_catalog/plant_catalog/settings.py +++ b/plant_catalog/plant_catalog/settings.py @@ -26,7 +26,7 @@ SECRET_KEY = 'django-insecure-(e9sx(r=75hv%xni#rcm4)0&67c34+her^!%7dqlghem1=l0lc # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False -ALLOWED_HOSTS = ['gonzalohd.eu', 'plantsapi.gonzalohd.eu', 'localhost', '192.168.1.139', '127.0.0.1'] +ALLOWED_HOSTS = ['gonzalohd.com', 'plantsapi.gonzalohd.com', 'localhost', '192.168.1.139', '127.0.0.1'] # ALLOWED_HOSTS = ['*']