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:
commit
92a52d65f2
11 changed files with 111 additions and 34 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 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, you’ll 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, you’ll need to configure it to use the exposed port (default: 8016).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
documentation/public/unraid-config-1.png
Normal file
BIN
documentation/public/unraid-config-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 337 KiB |
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 224 KiB |
BIN
documentation/public/unraid-config-3.png
Normal file
BIN
documentation/public/unraid-config-3.png
Normal file
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 |
|
@ -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}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue