11 changed files with 159 additions and 2 deletions
-
1apps/account/admin/__init__.py
-
58apps/account/admin/location.py
-
4apps/account/admin/user.py
-
30apps/account/migrations/0003_locationhistory.py
-
13apps/account/models/user.py
-
1apps/account/serializers/__init__.py
-
10apps/account/serializers/location_history.py
-
1apps/account/urls.py
-
1apps/account/views/__init__.py
-
36apps/account/views/location_history.py
-
4sshs
@ -0,0 +1,58 @@ |
|||
from django.contrib import admin |
|||
from django.utils.translation import gettext_lazy as _ |
|||
from unfold.admin import ModelAdmin, TabularInline |
|||
from unfold.decorators import display |
|||
from unfold.contrib.filters.admin import ( |
|||
RangeDateTimeFilter, |
|||
TextFilter, |
|||
AutocompleteSelectFilter, |
|||
) |
|||
|
|||
from apps.account.models import LocationHistory, User |
|||
from utils.admin import project_admin_site |
|||
|
|||
|
|||
class LocationHistoryInline(TabularInline): |
|||
model = LocationHistory |
|||
extra = 0 |
|||
tab = True |
|||
fields = ('lat', 'lon', 'country', 'city', 'selected_manually', 'ip', 'timezone', 'at_time') |
|||
readonly_fields = ('lat', 'lon', 'country', 'city', 'selected_manually', 'ip', 'timezone', 'at_time',) |
|||
verbose_name = _("Location History") |
|||
verbose_name_plural = _("Location History") |
|||
can_delete = False |
|||
show_change_link = True |
|||
|
|||
|
|||
class LocationHistoryAdmin(ModelAdmin): |
|||
list_display = ('user', 'display_location', 'country', 'city', 'selected_manually', 'ip', 'display_at_time') |
|||
list_filter = [ |
|||
('user', AutocompleteSelectFilter), |
|||
'country', |
|||
'city', |
|||
'selected_manually', |
|||
('at_time', RangeDateTimeFilter), |
|||
] |
|||
search_fields = ('user__email', 'user__fullname', 'country', 'city', 'ip') |
|||
readonly_fields = ('at_time',) |
|||
fieldsets = ( |
|||
(None, { |
|||
'fields': ('user', ('lat', 'lon'), ('country', 'city')) |
|||
}), |
|||
(_('Additional Information'), { |
|||
'fields': ('selected_manually', 'ip', 'timezone', 'at_time'), |
|||
'classes': ('tab',), |
|||
}), |
|||
) |
|||
|
|||
@display(description=_("Location")) |
|||
def display_location(self, instance: LocationHistory): |
|||
return f"{instance.lat}, {instance.lon}" |
|||
|
|||
@display(description=_("Date & Time")) |
|||
def display_at_time(self, instance: LocationHistory): |
|||
return instance.at_time.strftime("%Y-%m-%d %H:%M") if instance.at_time else "-" |
|||
|
|||
|
|||
# Register with project admin site |
|||
project_admin_site.register(LocationHistory, LocationHistoryAdmin) |
|||
@ -0,0 +1,30 @@ |
|||
# Generated by Django 5.1.8 on 2025-05-01 15:54 |
|||
|
|||
import django.db.models.deletion |
|||
from django.conf import settings |
|||
from django.db import migrations, models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
dependencies = [ |
|||
('account', '0002_alter_user_phone_number'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.CreateModel( |
|||
name='LocationHistory', |
|||
fields=[ |
|||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('lat', models.FloatField(verbose_name='lat')), |
|||
('lon', models.FloatField(verbose_name='lon')), |
|||
('country', models.CharField(blank=True, max_length=255, null=True, verbose_name='country')), |
|||
('city', models.CharField(blank=True, max_length=255, null=True, verbose_name='city')), |
|||
('selected_manually', models.BooleanField(blank=True, null=True)), |
|||
('ip', models.CharField(blank=True, max_length=255, null=True)), |
|||
('timezone', models.CharField(blank=True, max_length=60, null=True)), |
|||
('at_time', models.DateTimeField(auto_now_add=True)), |
|||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='location_history', to=settings.AUTH_USER_MODEL)), |
|||
], |
|||
), |
|||
] |
|||
@ -1,2 +1,3 @@ |
|||
from .user import * |
|||
from .notification import * |
|||
from .location_history import * |
|||
@ -0,0 +1,10 @@ |
|||
from rest_framework import serializers |
|||
|
|||
from apps.account.models import LocationHistory |
|||
|
|||
|
|||
class LocationHistorySerializer(serializers.ModelSerializer): |
|||
user = serializers.HiddenField(default=serializers.CurrentUserDefault()) |
|||
class Meta: |
|||
model = LocationHistory |
|||
exclude = ('at_time',) |
|||
@ -1,4 +1,5 @@ |
|||
from .user import * |
|||
from .notification import * |
|||
from .location_history import * |
|||
|
|||
|
|||
@ -0,0 +1,36 @@ |
|||
from rest_framework.mixins import CreateModelMixin |
|||
from rest_framework.permissions import IsAuthenticated |
|||
from rest_framework.generics import GenericAPIView |
|||
from rest_framework.response import Response |
|||
from rest_framework import status |
|||
|
|||
from apps.account.models import LocationHistory |
|||
from apps.account.serializers import LocationHistorySerializer |
|||
|
|||
|
|||
class LocationHistoryView(GenericAPIView, CreateModelMixin): |
|||
permission_classes = [IsAuthenticated] |
|||
serializer_class = LocationHistorySerializer |
|||
|
|||
def post(self, request, *args, **kwargs): |
|||
ip = self.get_client_ip() |
|||
data = request.data.copy() |
|||
data['ip'] = ip |
|||
serializer = self.get_serializer(data=data) |
|||
|
|||
if serializer.is_valid(): |
|||
serializer.save(user=request.user) |
|||
return Response(serializer.data, status=status.HTTP_201_CREATED) |
|||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) |
|||
|
|||
def get_queryset(self): |
|||
return LocationHistory.objects.filter(user=self.request.user) |
|||
|
|||
def get_client_ip(self): |
|||
# Retrieve the client's IP address from the request headers |
|||
x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR') |
|||
if x_forwarded_for: |
|||
ip = x_forwarded_for.split(',')[0] |
|||
else: |
|||
ip = self.request.META.get('REMOTE_ADDR') |
|||
return ip |
|||
@ -1,3 +1,7 @@ |
|||
Host 62.60.197.179 |
|||
HostName 62.60.197.179 |
|||
User ubuntu |
|||
|
|||
Host 88.99.212.243 |
|||
HostName 88.99.212.243 |
|||
User nwhco |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue