feat: Implement Entity Suggestion Manager and Modal components

- Added EntitySuggestionManager.vue to manage entity suggestions and authentication.
- Created EntitySuggestionModal.vue for displaying suggestions and adding new entities.
- Integrated AuthManager for user authentication within the suggestion modal.
- Enhanced signal handling in start-servers.sh for graceful shutdown of servers.
- Improved server startup script to ensure proper cleanup and responsiveness to termination signals.
- Added documentation for signal handling fixes and usage instructions.
This commit is contained in:
pacnpal
2025-08-25 10:46:54 -04:00
parent 937eee19e4
commit dcf890a55c
61 changed files with 10328 additions and 740 deletions

View File

@@ -17,19 +17,26 @@ class UserSerializer(serializers.ModelSerializer):
"""
User serializer for API responses
"""
avatar_url = serializers.SerializerMethodField()
class Meta:
model = User
fields = [
'id', 'username', 'email', 'first_name', 'last_name',
'date_joined', 'is_active', 'avatar_url'
"id",
"username",
"email",
"first_name",
"last_name",
"date_joined",
"is_active",
"avatar_url",
]
read_only_fields = ['id', 'date_joined', 'is_active']
read_only_fields = ["id", "date_joined", "is_active"]
def get_avatar_url(self, obj):
"""Get user avatar URL"""
if hasattr(obj, 'profile') and obj.profile.avatar:
if hasattr(obj, "profile") and obj.profile.avatar:
return obj.profile.avatar.url
return None
@@ -38,59 +45,57 @@ class LoginSerializer(serializers.Serializer):
"""
Serializer for user login
"""
username = serializers.CharField(
max_length=254,
help_text="Username or email address"
max_length=254, help_text="Username or email address"
)
password = serializers.CharField(
max_length=128,
style={'input_type': 'password'},
trim_whitespace=False
max_length=128, style={"input_type": "password"}, trim_whitespace=False
)
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
username = attrs.get("username")
password = attrs.get("password")
if username and password:
return attrs
raise serializers.ValidationError(
'Must include username/email and password.'
)
raise serializers.ValidationError("Must include username/email and password.")
class SignupSerializer(serializers.ModelSerializer):
"""
Serializer for user registration
"""
password = serializers.CharField(
write_only=True,
validators=[validate_password],
style={'input_type': 'password'}
style={"input_type": "password"},
)
password_confirm = serializers.CharField(
write_only=True,
style={'input_type': 'password'}
write_only=True, style={"input_type": "password"}
)
class Meta:
model = User
fields = [
'username', 'email', 'first_name', 'last_name',
'password', 'password_confirm'
"username",
"email",
"first_name",
"last_name",
"password",
"password_confirm",
]
extra_kwargs = {
'password': {'write_only': True},
'email': {'required': True},
"password": {"write_only": True},
"email": {"required": True},
}
def validate_email(self, value):
"""Validate email is unique"""
if UserModel.objects.filter(email=value).exists():
raise serializers.ValidationError(
"A user with this email already exists."
)
raise serializers.ValidationError("A user with this email already exists.")
return value
def validate_username(self, value):
@@ -103,24 +108,22 @@ class SignupSerializer(serializers.ModelSerializer):
def validate(self, attrs):
"""Validate passwords match"""
password = attrs.get('password')
password_confirm = attrs.get('password_confirm')
password = attrs.get("password")
password_confirm = attrs.get("password_confirm")
if password != password_confirm:
raise serializers.ValidationError({
'password_confirm': 'Passwords do not match.'
})
raise serializers.ValidationError(
{"password_confirm": "Passwords do not match."}
)
return attrs
def create(self, validated_data):
"""Create user with validated data"""
validated_data.pop('password_confirm', None)
password = validated_data.pop('password')
validated_data.pop("password_confirm", None)
password = validated_data.pop("password")
user = UserModel.objects.create(
**validated_data
)
user = UserModel.objects.create(**validated_data)
user.set_password(password)
user.save()
@@ -131,6 +134,7 @@ class PasswordResetSerializer(serializers.Serializer):
"""
Serializer for password reset request
"""
email = serializers.EmailField()
def validate_email(self, value):
@@ -145,37 +149,36 @@ class PasswordResetSerializer(serializers.Serializer):
def save(self, **kwargs):
"""Send password reset email if user exists"""
if hasattr(self, 'user'):
if hasattr(self, "user"):
# Create password reset token
token = get_random_string(64)
PasswordReset.objects.update_or_create(
user=self.user,
defaults={
'token': token,
'expires_at': timezone.now() + timedelta(hours=24),
'used': False
}
"token": token,
"expires_at": timezone.now() + timedelta(hours=24),
"used": False,
},
)
# Send reset email
request = self.context.get('request')
request = self.context.get("request")
if request:
site = get_current_site(request)
reset_url = f"{request.scheme}://{site.domain}/reset-password/{token}/"
context = {
'user': self.user,
'reset_url': reset_url,
'site_name': site.name,
"user": self.user,
"reset_url": reset_url,
"site_name": site.name,
}
email_html = render_to_string(
'accounts/email/password_reset.html',
context
"accounts/email/password_reset.html", context
)
EmailService.send_email(
to=getattr(self.user, 'email', None),
to=getattr(self.user, "email", None),
subject="Reset your password",
text=f"Click the link to reset your password: {reset_url}",
site=site,
@@ -187,49 +190,45 @@ class PasswordChangeSerializer(serializers.Serializer):
"""
Serializer for password change
"""
old_password = serializers.CharField(
max_length=128,
style={'input_type': 'password'}
max_length=128, style={"input_type": "password"}
)
new_password = serializers.CharField(
max_length=128,
validators=[validate_password],
style={'input_type': 'password'}
max_length=128, validators=[validate_password], style={"input_type": "password"}
)
new_password_confirm = serializers.CharField(
max_length=128,
style={'input_type': 'password'}
max_length=128, style={"input_type": "password"}
)
def validate_old_password(self, value):
"""Validate old password is correct"""
user = self.context['request'].user
user = self.context["request"].user
if not user.check_password(value):
raise serializers.ValidationError(
'Old password is incorrect.'
)
raise serializers.ValidationError("Old password is incorrect.")
return value
def validate(self, attrs):
"""Validate new passwords match"""
new_password = attrs.get('new_password')
new_password_confirm = attrs.get('new_password_confirm')
new_password = attrs.get("new_password")
new_password_confirm = attrs.get("new_password_confirm")
if new_password != new_password_confirm:
raise serializers.ValidationError({
'new_password_confirm': 'New passwords do not match.'
})
raise serializers.ValidationError(
{"new_password_confirm": "New passwords do not match."}
)
return attrs
def save(self, **kwargs):
"""Change user password"""
user = self.context['request'].user
new_password = self.initial_data.get(
'new_password') if self.initial_data else None
user = self.context["request"].user
new_password = (
self.initial_data.get("new_password") if self.initial_data else None
)
if new_password is None:
raise serializers.ValidationError('New password is required.')
raise serializers.ValidationError("New password is required.")
user.set_password(new_password)
user.save()
@@ -241,6 +240,7 @@ class SocialProviderSerializer(serializers.Serializer):
"""
Serializer for social authentication providers
"""
id = serializers.CharField()
name = serializers.CharField()
login_url = serializers.URLField()