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

Merge branch 'development' into dependabot/pip/backend/server/pip-607b783c7d

This commit is contained in:
Sean Morley 2025-01-14 22:02:30 -05:00 committed by GitHub
commit 92a52d65f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 111 additions and 34 deletions

View file

@ -20,7 +20,7 @@ from django.contrib.auth import get_user_model
from icalendar import Calendar, Event, vText, vCalAddress from icalendar import Calendar, Event, vText, vCalAddress
from django.http import HttpResponse from django.http import HttpResponse
from datetime import datetime from datetime import datetime
from django.db.models import Min from django.db.models import Max
User = get_user_model() User = get_user_model()
@ -54,9 +54,9 @@ class AdventureViewSet(viewsets.ModelViewSet):
if order_by == 'date': if order_by == 'date':
# order by the earliest visit object associated with the adventure # order by the earliest visit object associated with the adventure
queryset = queryset.annotate(earliest_visit=Min('visits__start_date')) queryset = queryset.annotate(latest_visit=Max('visits__start_date'))
queryset = queryset.filter(earliest_visit__isnull=False) queryset = queryset.filter(latest_visit__isnull=False)
ordering = 'earliest_visit' ordering = 'latest_visit'
# Apply case-insensitive sorting for the 'name' field # Apply case-insensitive sorting for the 'name' field
elif order_by == 'name': elif order_by == 'name':
queryset = queryset.annotate(lower_name=Lower('name')) queryset = queryset.annotate(lower_name=Lower('name'))
@ -1163,6 +1163,7 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
display_name = None display_name = None
country_code = None country_code = None
city = None city = None
visited_city = None
# town = None # town = None
# city = None # city = None
@ -1221,6 +1222,8 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
# searches through all of the users adventures, if the serialized data is_visited, is true, runs reverse geocode on the adventures and if a region is found, marks it as visited. Use the extractIsoCode function to get the region # searches through all of the users adventures, if the serialized data is_visited, is true, runs reverse geocode on the adventures and if a region is found, marks it as visited. Use the extractIsoCode function to get the region
new_region_count = 0 new_region_count = 0
new_regions = {} new_regions = {}
new_city_count = 0
new_cities = {}
adventures = Adventure.objects.filter(user_id=self.request.user) adventures = Adventure.objects.filter(user_id=self.request.user)
serializer = AdventureSerializer(adventures, many=True) serializer = AdventureSerializer(adventures, many=True)
for adventure, serialized_adventure in zip(adventures, serializer.data): for adventure, serialized_adventure in zip(adventures, serializer.data):
@ -1234,17 +1237,25 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
data = response.json() data = response.json()
except requests.exceptions.JSONDecodeError: except requests.exceptions.JSONDecodeError:
return Response({"error": "Invalid response from geocoding service"}, status=400) return Response({"error": "Invalid response from geocoding service"}, status=400)
region = self.extractIsoCode(data) extracted_region = self.extractIsoCode(data)
if 'error' not in region: if 'error' not in extracted_region:
region = Region.objects.filter(id=region['id']).first() region = Region.objects.filter(id=extracted_region['region_id']).first()
visited_region = VisitedRegion.objects.filter(region=region, user_id=self.request.user).first() visited_region = VisitedRegion.objects.filter(region=region, user_id=self.request.user).first()
if not visited_region: if not visited_region:
visited_region = VisitedRegion(region=region, user_id=self.request.user) visited_region = VisitedRegion(region=region, user_id=self.request.user)
visited_region.save() visited_region.save()
new_region_count += 1 new_region_count += 1
new_regions[region.id] = region.name new_regions[region.id] = region.name
return Response({"new_regions": new_region_count, "regions": new_regions})
if extracted_region['city_id'] is not None:
city = City.objects.filter(id=extracted_region['city_id']).first()
visited_city = VisitedCity.objects.filter(city=city, user_id=self.request.user).first()
if not visited_city:
visited_city = VisitedCity(city=city, user_id=self.request.user)
visited_city.save()
new_city_count += 1
new_cities[city.id] = city.name
return Response({"new_regions": new_region_count, "regions": new_regions, "new_cities": new_city_count, "cities": new_cities})
from django.http import HttpResponse from django.http import HttpResponse
from rest_framework import viewsets from rest_framework import viewsets

View file

@ -1,4 +1,5 @@
Django==5.0.11 Django==5.0.11
Django==5.0.10
djangorestframework>=3.15.2 djangorestframework>=3.15.2
django-allauth==0.63.3 django-allauth==0.63.3
drf-yasg==1.21.4 drf-yasg==1.21.4

View file

@ -1,17 +0,0 @@
from django import forms
class CustomSignupForm(forms.Form):
first_name = forms.CharField(max_length=30, required=True)
last_name = forms.CharField(max_length=30, required=True)
def signup(self, request, user):
# Delay the import to avoid circular import
from allauth.account.forms import SignupForm
# No need to call super() from CustomSignupForm; use the SignupForm directly if needed
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
# Save the user instance
user.save()
return user

View file

@ -1,3 +1,84 @@
from django.test import TestCase from rest_framework.test import APITestCase
from .models import CustomUser
from uuid import UUID
# Create your tests here. from allauth.account.models import EmailAddress
class UserAPITestCase(APITestCase):
def setUp(self):
# Signup a new user
response = self.client.post('/_allauth/browser/v1/auth/signup', {
'username': 'testuser',
'email': 'testuser@example.com',
'password': 'testpassword',
'first_name': 'Test',
'last_name': 'User',
}, format='json')
self.assertEqual(response.status_code, 200)
def test_001_user(self):
# Fetch user metadata
response = self.client.get('/auth/user-metadata/', format='json')
self.assertEqual(response.status_code, 200)
data = response.json()
print(data)
self.assertEqual(data['username'], 'testuser')
self.assertEqual(data['email'], 'testuser@example.com')
self.assertEqual(data['first_name'], 'Test')
self.assertEqual(data['last_name'], 'User')
self.assertEqual(data['public_profile'], False)
self.assertEqual(data['profile_pic'], None)
self.assertEqual(UUID(data['uuid']), CustomUser.objects.get(username='testuser').uuid)
self.assertEqual(data['is_staff'], False)
self.assertEqual(data['has_password'], True)
def test_002_user_update(self):
try:
userModel = CustomUser.objects.get(username='testuser2')
except:
userModel = None
self.assertEqual(userModel, None)
# Update user metadata
response = self.client.patch('/auth/update-user/', {
'username': 'testuser2',
'first_name': 'Test2',
'last_name': 'User2',
'public_profile': True,
}, format='json')
self.assertEqual(response.status_code, 200)
data = response.json()
# Note that the email field is not updated because that is a seperate endpoint
userModel = CustomUser.objects.get(username='testuser2')
print(userModel)
self.assertEqual(data['username'], 'testuser2')
self.assertEqual(data['email'], 'testuser@example.com')
self.assertEqual(data['first_name'], 'Test2')
self.assertEqual(data['last_name'], 'User2')
self.assertEqual(data['public_profile'], True)
self.assertEqual(data['profile_pic'], None)
self.assertEqual(UUID(data['uuid']), CustomUser.objects.get(username='testuser2').uuid)
self.assertEqual(data['is_staff'], False)
self.assertEqual(data['has_password'], True)
def test_003_user_add_email(self):
# Update user email
response = self.client.post('/_allauth/browser/v1/account/email', {
'email': 'testuser2@example.com',
}, format='json')
self.assertEqual(response.status_code, 200)
data = response.json()
email_data = data['data'][0]
self.assertEqual(email_data['email'], 'testuser2@example.com')
self.assertEqual(email_data['primary'], False)
self.assertEqual(email_data['verified'], False)
emails = EmailAddress.objects.filter(user=CustomUser.objects.get(username='testuser'))
self.assertEqual(emails.count(), 2)
# assert email are testuser@example and testuser2@example.com
self.assertEqual(emails[1].email, 'testuser@example.com')
self.assertEqual(emails[0].email, 'testuser2@example.com')

View file

@ -13,19 +13,17 @@ It is recommended to install applications in this order.
- To find the Database Application, search for `PostGIS` on the Unraid App Store and fill out the fields as follows: - To find the Database Application, search for `PostGIS` on the Unraid App Store and fill out the fields as follows:
- Ensure that the POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB are set in the PostGIS container if not add them custom variables - Ensure that the POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB are set in the PostGIS container if not add them custom variables
![/static/img/unraid-config-2.png](/static/img/unraid-config-2.png) ![/static/img/unraid-config-2.png](/unraid-config-2.png)
## Backend ## Backend
- Cache Configuration: This option is useful only if your appdata share is stored on a cache drive, which is used to speed up read/write operations for your containerized applications. - Cache Configuration: This option is useful only if your appdata share is stored on a cache drive, which is used to speed up read/write operations for your containerized applications.
- Note: if your running the server in a docker network that is other than "host" (for example "bridge") than you need to add the IP of the host machine in the CSRF Trusted Origins variable. - Note: if your running the server in a docker network that is other than "host" (for example "bridge") than you need to add the IP of the host machine in the CSRF Trusted Origins variable.
![/static/img/unraid-config-1.png](/static/img/unraid-config-1.png) ![/static/img/unraid-config-1.png](/unraid-config-1.png)
## Frontend ## Frontend
- By default, the frontend connects to the backend using `http://server:8000`. This will work if both the frontend and backend are on the same network. Otherwise, youll need to configure it to use the exposed port (default: 8016). - By default, the frontend connects to the backend using `http://server:8000`. This will work if both the frontend and backend are on the same network. Otherwise, youll need to configure it to use the exposed port (default: 8016).
![/static/img/unraid-config-3.png](/static/img/unraid-config-3.png) ![/static/img/unraid-config-3.png](/unraid-config-3.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

View file

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 224 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 409 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

View file

@ -57,11 +57,14 @@
class="block input input-bordered w-full max-w-xs" class="block input input-bordered w-full max-w-xs"
/><br /> /><br />
{#if $page.form?.mfa_required} {#if $page.form?.mfa_required}
<label for="password">TOTP</label> <label for="totp">TOTP</label>
<input <input
type="password" type="text"
name="totp" name="totp"
id="totp" id="totp"
inputmode="numeric"
pattern="[0-9]*"
autocomplete="one-time-code"
class="block input input-bordered w-full max-w-xs" class="block input input-bordered w-full max-w-xs"
/><br /> /><br />
{/if} {/if}