Compare commits

...

4 commits

14 changed files with 277 additions and 31 deletions

View file

@ -3,8 +3,6 @@ from web.models.category import Category
class CategorySerializer(serializers.ModelSerializer):
# to_representation(self, instance) if needed
# fk = Serializer()
class Meta:
model = Category

View file

@ -1,12 +1,49 @@
from rest_framework import serializers
from django.forms.models import model_to_dict
from web.models.event import Event
from web.models.event_category import EventCategory
from web.models.event_tag import EventTag
class EventSerializer(serializers.ModelSerializer):
# to_representation(self, instance) if needed
# fk = Serializer()
class Meta:
model = Event
fields = '__all__'
def to_representation(self, instance):
representation = super().to_representation(instance)
event_categories = EventCategory.objects.filter(event=instance).filter(active=True).all()
event_tags = EventTag.objects.filter(event=instance).filter(active=True).all()
representation['location'] = {
'lng': instance.coordinates.x if instance.coordinates else None,
'lat': instance.coordinates.y if instance.coordinates else None
}
representation['categories'] = self._parse_categories(event_categories)
representation['tags'] = self._parse_tags(event_tags)
return representation
def _parse_categories(self, event_categories):
categories = []
if event_categories.exists():
for event_category in event_categories:
categories.append({
'id': event_category.category.id,
'name': event_category.category.name,
'description': event_category.category.description,
'_meta': model_to_dict(event_category)
})
return categories
def _parse_tags(self, event_tags):
tags = []
if event_tags.exists():
for event_tag in event_tags:
tags.append({
'id': event_tag.tag.id,
'name': event_tag.tag.name,
'_meta': model_to_dict(event_tag)
})
return tags

View file

@ -0,0 +1,10 @@
from rest_framework import serializers
from web.models.event_category import EventCategory
class EventCategorySerializer(serializers.ModelSerializer):
class Meta:
model = EventCategory
fields = "all"

View file

@ -0,0 +1,10 @@
from rest_framework import serializers
from web.models.event_tag import EventTag
class EventTagSerializer(serializers.ModelSerializer):
class Meta:
model = EventTag
fields = "all"

View file

@ -15,9 +15,15 @@ class BaseView(APIView):
def _build_multi_response(self, data):
serialized_data = []
for d in data:
serializer = self.SERIALIZER(data)
serializer = self.SERIALIZER(d)
serialized_data.append(serializer.data)
response = Response()
response.data = serialized_data
return response
def error_response(self, status_code, description):
response = Response()
response.status_code = status_code
response.data = {"error": description}
return response

View file

@ -1,21 +1,51 @@
import json
from django.contrib.gis.geos import Point
from django.contrib.gis.measure import D
from django.db import transaction
from django.shortcuts import get_object_or_404
from api.serializers.event import EventSerializer
from api.views.base import BaseView
from web.models.category import Category
from web.models.event_category import EventCategory
from web.models.event import Event
from web.models.tag import Tag
from web.models.event_tag import EventTag
class EventView(BaseView):
SERIALIZER = EventSerializer
def get(self, request, event_id):
event = get_object_or_404(Event, pk=event_id)
return self._build_response(event)
def get(self, request, event_id=None):
if event_id:
event = get_object_or_404(Event, pk=event_id, active=True)
return self._build_response(event)
else:
return self._get_events_by_radius(request)
def _get_events_by_radius(self, request):
lat = request.GET.get('lat')
lng = request.GET.get('lng')
r = request.GET.get('r')
if all([lat, lng, r]):
try:
user_location = Point(float(lng), float(lat), srid=4326)
events = Event.objects.filter(active=True).filter(coordinates__dwithin=(user_location, D(mi=float(r)))).all()
return self._build_multi_response(events)
except ValueError:
return self.error_response(400, 'Invalid coordinates or radius format')
else:
return self.error_response(400, 'Must include lat, lng, and r in query string')
@transaction.atomic
def post(self, request):
data = json.loads(request.body)
location = data.get('location', {})
coordinates = Point(location.get("lng"), location.get("lat"), srid=4326)
event = Event(
name=data.get('name'),
description=data.get('description'),
@ -29,16 +59,55 @@ class EventView(BaseView):
rain_date=data.get('rain_date'),
email=data.get('email'),
phone_number=data.get('phone_number'),
coordinates=coordinates,
)
event.save()
categories = data.get('categories', [])
self._create_event_categories(categories, event)
tag_names = data.get('tags', [])
self._create_event_tags(tag_names, event)
return self._build_response(event)
def _create_event_categories(self, categories, event):
event_categories = []
for category_id in categories:
category = get_object_or_404(Category, pk=category_id)
event_category = EventCategory(
event=event,
category=category
)
event_categories.append(event_category)
EventCategory.objects.bulk_create(event_categories)
def _create_event_tags(self, tag_names, event):
event_tags = []
for tag_name in tag_names:
tag, created = Tag.objects.get_or_create(name=tag_name)
event_tag = EventTag(
tag=tag,
event=event
)
event_tags.append(event_tag)
EventTag.objects.bulk_create(event_tags)
@transaction.atomic
def put(self, request, event_id):
data = json.loads(request.body)
event = get_object_or_404(Event, pk=event_id)
location = data.get('location', {})
if location:
coordinates = Point(location.get("lng"), location.get("lat"), srid=4326)
event.coordinates = coordinates
event.name = data.get('name', event.name)
event.description = data.get('description', event.description)
event.url = data.get('url', event.url)
@ -52,9 +121,64 @@ class EventView(BaseView):
event.email = data.get('email', event.email)
event.phone_number = data.get('phone_number', event.phone_number)
categories = data.get('categories', [])
self._update_event_categories(categories, event)
tag_names = data.get('tags', [])
self._update_event_tags(tag_names, event)
event.save()
self._build_response(event)
def _update_event_categories(self, categories, event):
if categories:
current_event_categories = EventCategory.objects.filter(event=event).filter(active=True).all()
current_event_category_map = {ec.category.id: ec for ec in current_event_categories}
for current_event_category in current_event_categories:
if current_event_category.category.id not in categories:
current_event_category.active = False
current_event_category.save()
new_event_categories = []
for category_id in categories:
category = get_object_or_404(Category, pk=category_id)
if category not in current_event_category_map:
event_category = EventCategory(
category=category,
event=event
)
new_event_categories.append(event_category)
if new_event_categories:
EventCategory.objects.bulk_create(new_event_categories)
def _update_event_tags(self, tag_names, event):
if tag_names:
current_event_tags = EventTag.objects.filter(event=event).filter(active=True).all()
current_event_tag_names = [x.name for x in current_event_tags]
for current_event_tag in current_event_tags:
if current_event_tag.tag.name not in tag_names:
current_event_tag.active = False
current_event_tag.save()
new_event_tags = []
for tag_name in tag_names:
if tag_name not in current_event_tag_names:
tag, created = Tag.objects.get_or_create(name=tag_name)
event_tag = EventTag(
tag=tag,
event=event
)
new_event_tags.append(event_tag)
if new_event_tags:
EventTag.objects.bulk_create(new_event_tags)
def delete(self, request, event_id):
event = get_object_or_404(Event, pk=event_id)
event.active = False

View file

@ -1,15 +1,3 @@
"""
Django settings for localist project.
Generated by 'django-admin startproject' using Django 6.0.6.
For more information on this file, see
https://docs.djangoproject.com/en/6.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/6.0/ref/settings/
"""
from pathlib import Path
from config import DB_NAME, DB_USER, DB_HOST, DB_PASSWORD, DB_PORT, SECRET_KEY
@ -33,6 +21,8 @@ ALLOWED_HOSTS = []
INSTALLED_APPS = [
"corsheaders",
"django.contrib.gis",
"leaflet",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
@ -81,7 +71,7 @@ WSGI_APPLICATION = "localist.wsgi.application"
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"ENGINE": "django.contrib.gis.db.backends.postgis",
"NAME": DB_NAME,
"USER": DB_USER,
"PASSWORD": DB_PASSWORD,
@ -131,4 +121,4 @@ STATIC_URL = "static/"
CORS_ALLOWED_ORIGINS = [
"http://localhost:5173",
"http://127.0.0.1:5173",
]
]

View file

@ -2,6 +2,8 @@ asgiref==3.11.1
certifi==2026.5.20
charset-normalizer==3.4.7
Django==6.0.6
django-cors-headers==4.0.0
django-leaflet==0.33.0
django-rest-framework==0.1.0
djangorestframework==3.17.1
idna==3.18
@ -10,5 +12,3 @@ psycopg-binary==3.3.4
requests==2.34.2
sqlparse==0.5.5
urllib3==2.7.0
django-cors-headers==4.0.0

View file

@ -3,7 +3,7 @@ import requests
url = "http://127.0.0.1:8000/api/event"
data = {
"name": "Test Event 2",
"name": "Test Event 3",
"description": "this is a test",
"url": "https://www.domdit.com",
"address": "31 Fleetwood Dr Hazlet Nj",
@ -14,8 +14,11 @@ data = {
"end_time": "2026-08-14T01:34:38Z",
"rain_date": "2026-06-15T01:34:42Z",
"email": "me@domdit.com",
"phone_number": ""
"phone_number": "",
"categories": [1],
"tags": ["tag1", "tag2", "tag4"]
}
resp = requests.post(url, data=data)
resp = requests.post(url, json=data)
print(resp.status_code)

View file

@ -0,0 +1,23 @@
# Generated by Django 6.0.6 on 2026-06-21 00:12
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("web", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="eventcategory",
name="category",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="category",
to="web.category",
),
),
]

View file

@ -0,0 +1,21 @@
# Generated by Django 6.0.6 on 2026-06-21 17:56
import django.contrib.gis.db.models.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("web", "0002_alter_eventcategory_category"),
]
operations = [
migrations.AddField(
model_name="event",
name="coordinates",
field=django.contrib.gis.db.models.fields.PointField(
blank=True, default=None, null=True, srid=4326
),
),
]

View file

@ -0,0 +1,21 @@
# Generated by Django 6.0.6 on 2026-06-21 19:33
import django.contrib.gis.db.models.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("web", "0003_event_coordinates"),
]
operations = [
migrations.AlterField(
model_name="event",
name="coordinates",
field=django.contrib.gis.db.models.fields.PointField(
blank=True, default=None, geography=True, null=True, srid=4326
),
),
]

View file

@ -1,7 +1,7 @@
from django.db import models
from django.contrib import admin
from leaflet.admin import LeafletGeoAdmin
from django.contrib.gis.db import models
from web.models.base import BaseModel
from enum import StrEnum
class Event(BaseModel):
@ -14,6 +14,7 @@ class Event(BaseModel):
description = models.TextField()
url = models.URLField()
address = models.CharField()
coordinates = models.PointField(blank=True, default=None, null=True, srid=4326, geography=True)
status = models.CharField(max_length=20, choices=Status.choices, default=Status.SCHEDULED)
price = models.DecimalField(max_digits=10, default=None, blank=True, decimal_places=2)
require_rsvp = models.BooleanField()
@ -29,7 +30,7 @@ class Event(BaseModel):
db_table = 'events'
class EventAdmin(admin.ModelAdmin):
class EventAdmin(LeafletGeoAdmin):
search_fields = (
'name',
'description',
@ -38,6 +39,7 @@ class EventAdmin(admin.ModelAdmin):
'rain_date',
'url',
'address',
'coordinates',
'status',
'price',
'require_rsvp',
@ -51,6 +53,7 @@ class EventAdmin(admin.ModelAdmin):
'rain_date',
'url',
'address',
'coordinates',
'status',
'price',
'require_rsvp',

View file

@ -7,7 +7,7 @@ from web.models.category import Category
class EventCategory(BaseModel):
event = models.ForeignKey(Event, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="category")
class Meta:
db_table = 'event_categories'