1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-25 07:49:37 +02:00

Enhance adventure management: add error handling for category fetch, implement unique email constraint in user model, and update adventure save logic to ensure category assignment

This commit is contained in:
Sean Morley 2024-11-22 17:03:02 -05:00
parent 86d213bb8b
commit 736ede2417
15 changed files with 216 additions and 60 deletions

View file

@ -0,0 +1,19 @@
# Generated by Django 5.0.8 on 2024-11-17 21:43
from django.conf import settings
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('adventures', '0013_remove_adventure_type_alter_adventure_category'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterUniqueTogether(
name='category',
unique_together={('name', 'user_id')},
),
]

View file

@ -1,4 +1,5 @@
from collections.abc import Collection
from typing import Iterable
import uuid
from django.db import models
@ -101,6 +102,17 @@ class Adventure(models.Model):
if self.category:
if self.user_id != self.category.user_id:
raise ValidationError('Adventures must be associated with categories owned by the same user. Category owner: ' + self.category.user_id.username + ' Adventure owner: ' + self.user_id.username)
def save(self, force_insert: bool = False, force_update: bool = False, using: str | None = None, update_fields: Iterable[str] | None = None) -> None:
"""
Saves the current instance. If the instance is being inserted for the first time, it will be created in the database.
If it already exists, it will be updated.
"""
if force_insert and force_update:
raise ValueError("Cannot force both insert and updating in model saving.")
if not self.category:
self.category = Category.objects.get_or_create(user_id=self.user_id, name='general', display_name='General', icon='🌎')[0]
return super().save(force_insert, force_update, using, update_fields)
def __str__(self):
return self.name

View file

@ -37,7 +37,7 @@ class CategorySerializer(CustomModelSerializer):
def get_num_adventures(self, obj):
return Adventure.objects.filter(category=obj, user_id=obj.user_id).count()
class VisitSerializer(CustomModelSerializer):
class VisitSerializer(serializers.ModelSerializer):
class Meta:
model = Visit
@ -47,13 +47,34 @@ class VisitSerializer(CustomModelSerializer):
class AdventureSerializer(CustomModelSerializer):
images = AdventureImageSerializer(many=True, read_only=True)
visits = VisitSerializer(many=True, read_only=False)
category = CategorySerializer(read_only=True)
category = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(),
write_only=True,
required=False
)
category_object = CategorySerializer(source='category', read_only=True)
is_visited = serializers.SerializerMethodField()
class Meta:
model = Adventure
fields = ['id', 'user_id', 'name', 'description', 'rating', 'activity_types', 'location', 'is_public', 'collection', 'created_at', 'updated_at', 'images', 'link', 'longitude', 'latitude', 'visits', 'is_visited', 'category']
fields = [
'id', 'user_id', 'name', 'description', 'rating', 'activity_types', 'location',
'is_public', 'collection', 'created_at', 'updated_at', 'images', 'link', 'longitude',
'latitude', 'visits', 'is_visited', 'category', 'category_object'
]
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'is_visited']
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['category'] = representation.pop('category_object')
return representation
def validate_category(self, category):
# Check that the category belongs to the same user
if category.user_id != self.context['request'].user:
raise serializers.ValidationError('Category does not belong to the user.')
return category
def get_is_visited(self, obj):
current_date = timezone.now().date()
for visit in obj.visits.all():
@ -62,32 +83,24 @@ class AdventureSerializer(CustomModelSerializer):
elif visit.start_date and not visit.end_date and (visit.start_date <= current_date):
return True
return False
def to_representation(self, instance):
representation = super().to_representation(instance)
return representation
def create(self, validated_data):
visits_data = validated_data.pop('visits', [])
adventure = Adventure.objects.create(**validated_data)
for visit_data in visits_data:
Visit.objects.create(adventure=adventure, **visit_data)
return adventure
def update(self, instance, validated_data):
visits_data = validated_data.pop('visits', [])
# Update Adventure fields
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
# Get current visits
current_visits = instance.visits.all()
current_visit_ids = set(current_visits.values_list('id', flat=True))
# Update or create visits
updated_visit_ids = set()
for visit_data in visits_data:
visit_id = visit_data.get('id')
@ -98,16 +111,14 @@ class AdventureSerializer(CustomModelSerializer):
visit.save()
updated_visit_ids.add(visit_id)
else:
# If no ID is provided or ID doesn't exist, create new visit
new_visit = Visit.objects.create(adventure=instance, **visit_data)
updated_visit_ids.add(new_visit.id)
# Delete visits that are not in the updated data
visits_to_delete = current_visit_ids - updated_visit_ids
instance.visits.filter(id__in=visits_to_delete).delete()
return instance
class TransportationSerializer(CustomModelSerializer):
class Meta:

View file

@ -116,8 +116,8 @@ class AdventureViewSet(viewsets.ModelViewSet):
if not Category.objects.filter(user_id=request.user, name=type).exists():
return Response({"error": f"Category {type} does not exist"}, status=400)
if not types:
return Response({"error": "No valid types provided"}, status=400)
if not types:
return Response({"error": "At least one type must be provided"}, status=400)
queryset = Adventure.objects.filter(
category__in=Category.objects.filter(name__in=types, user_id=request.user),

View file

@ -0,0 +1,27 @@
# Generated by Django 5.0.8 on 2024-11-18 14:51
from django.db import migrations, models
def check_duplicate_email(apps, schema_editor):
# sets an email to null if there are duplicates
CustomUser = apps.get_model('users', 'CustomUser')
duplicates = CustomUser.objects.values('email').annotate(email_count=models.Count('email')).filter(email_count__gt=1)
for duplicate in duplicates:
CustomUser.objects.filter(email=duplicate['email']).update(email=None)
print(f"Duplicate email: {duplicate['email']}")
class Migration(migrations.Migration):
dependencies = [
('users', '0002_customuser_public_profile'),
]
operations = [
migrations.RunPython(check_duplicate_email),
migrations.AlterField(
model_name='customuser',
name='email',
field=models.EmailField(max_length=254, unique=True),
),
]

View file

@ -4,6 +4,7 @@ from django.db import models
from django_resized import ResizedImageField
class CustomUser(AbstractUser):
email = models.EmailField(unique=True) # Override the email field with unique constraint
profile_pic = ResizedImageField(force_format="WEBP", quality=75, null=True, blank=True, upload_to='profile-pics/')
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
public_profile = models.BooleanField(default=False)

View file

@ -63,6 +63,11 @@ class RegisterSerializer(serializers.Serializer):
def validate(self, data):
if data['password1'] != data['password2']:
raise serializers.ValidationError(_("The two password fields didn't match."))
# check if a user with the same email already exists
if User.objects.filter(email=data['email']).exists():
raise serializers.ValidationError("This email is already in use.")
return data
def custom_signup(self, request, user):