Compare commits
14 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 058a64b4a9 | |||
| 92db8d61ed | |||
| 7fcca38986 | |||
| bc46ca831c | |||
| 175ffda130 | |||
| 5448fd9d3c | |||
| 29da461a53 | |||
| c36e52d4fc | |||
| 72a29a6b8b | |||
| af65d4b57e | |||
| a061837802 | |||
| 56d0c52d42 | |||
| fbd2b76e0d | |||
| 04d947b288 |
52 changed files with 823 additions and 50 deletions
217
.gitignore
vendored
Normal file
217
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[codz]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
# Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
# poetry.lock
|
||||
# poetry.toml
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||
# pdm.lock
|
||||
# pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# pixi
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||
# pixi.lock
|
||||
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||
.pixi
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# Redis
|
||||
*.rdb
|
||||
*.aof
|
||||
*.pid
|
||||
|
||||
# RabbitMQ
|
||||
mnesia/
|
||||
rabbitmq/
|
||||
rabbitmq-data/
|
||||
|
||||
# ActiveMQ
|
||||
activemq-data/
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.envrc
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
# .idea/
|
||||
|
||||
# Abstra
|
||||
# Abstra is an AI-powered process automation framework.
|
||||
# Ignore directories containing user credentials, local state, and settings.
|
||||
# Learn more at https://abstra.io/docs
|
||||
.abstra/
|
||||
|
||||
# Visual Studio Code
|
||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||
# you could uncomment the following to ignore the entire vscode folder
|
||||
# .vscode/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
# Marimo
|
||||
marimo/_static/
|
||||
marimo/_lsp/
|
||||
__marimo__/
|
||||
|
||||
# Streamlit
|
||||
.streamlit/secrets.toml
|
||||
config*
|
||||
23
README.md
23
README.md
|
|
@ -1,13 +1,18 @@
|
|||
# Summary
|
||||
|
||||
A local app for local people.
|
||||
A community-driven "Things To Do Near Me" platform that combines features of interactive maps, events, and local social recommendations.
|
||||
Discover and share local events, hidden gems, activities, and experiences on an interactive map.
|
||||
# Detailed Design
|
||||
|
||||
User should be able to create an event, search for events (MVPs), and share events (future).
|
||||
Two users groups: Users who create events, and users who search for events. Users can be in both groups.
|
||||
Interactive map with pins for events, and a list view of events.
|
||||
Users can filter events by radius, category, tag, date, and location, etc.
|
||||
## Functionality
|
||||
|
||||
### User Creates an Event
|
||||
|
||||
- Fills out form
|
||||
- form includes:
|
||||
- Form includes:
|
||||
- Event Address
|
||||
- Event Name
|
||||
- Event Description
|
||||
|
|
@ -44,11 +49,21 @@
|
|||
## User Flow
|
||||
|
||||
## Web Pages
|
||||
|
||||
1. An interactive map page with pins for events, and a list view of events.
|
||||
2. An event creation page with a form for users to fill out.
|
||||
3. An event detail page that shows more information about the event.
|
||||
4. An event editing page that allows users to edit or cancel their events.
|
||||
5. An introduction page that explains the app and its features.
|
||||
## DB Structure
|
||||
|
||||
## API Endpoints
|
||||
|
||||
## Authentication
|
||||
Unique link for each event that can be used for editing and cancelling.
|
||||
|
||||
Future step:
|
||||
- User accounts with email and password, or social login (Google, Facebook, etc.)
|
||||
|
||||
# Dependencies
|
||||
- Frontend: React, Leaflet for interactive maps, and a UI library like Material-UI or Tailwind CSS.
|
||||
- Backend: Django for API development, SQLite for database management, and like AWS S3 for image storage.
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,3 +0,0 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
10
api/serializers/category.py
Normal file
10
api/serializers/category.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from rest_framework import serializers
|
||||
from web.models.category import Category
|
||||
|
||||
|
||||
class CategorySerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Category
|
||||
fields = '__all__'
|
||||
|
||||
49
api/serializers/event.py
Normal file
49
api/serializers/event.py
Normal file
|
|
@ -0,0 +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):
|
||||
|
||||
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
|
||||
10
api/serializers/event_category.py
Normal file
10
api/serializers/event_category.py
Normal 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"
|
||||
10
api/serializers/event_tag.py
Normal file
10
api/serializers/event_tag.py
Normal 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"
|
||||
12
api/serializers/tag.py
Normal file
12
api/serializers/tag.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from rest_framework import serializers
|
||||
from web.models.tag import Tag
|
||||
|
||||
|
||||
class TagSerializer(serializers.ModelSerializer):
|
||||
# to_representation(self, instance) if needed
|
||||
# fk = Serializer()
|
||||
|
||||
class Meta:
|
||||
model = Tag
|
||||
fields = '__all__'
|
||||
|
||||
0
api/services/event.py
Normal file
0
api/services/event.py
Normal file
|
|
@ -1,3 +0,0 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
29
api/views/base.py
Normal file
29
api/views/base.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
class BaseView(APIView):
|
||||
SERIALIZER = None
|
||||
|
||||
def _build_response(self, data):
|
||||
model_serializer = self.SERIALIZER(data)
|
||||
|
||||
response = Response()
|
||||
response.data = model_serializer.data
|
||||
return response
|
||||
|
||||
def _build_multi_response(self, data):
|
||||
serialized_data = []
|
||||
for d in 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
|
||||
44
api/views/category.py
Normal file
44
api/views/category.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import json
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from api.serializers.category import CategorySerializer
|
||||
from api.views.base import BaseView
|
||||
from web.models.category import Category
|
||||
|
||||
|
||||
class CategoryView(BaseView):
|
||||
SERIALIZER = CategorySerializer
|
||||
|
||||
def get(self, request, category_id=None):
|
||||
if category_id:
|
||||
category = get_object_or_404(Category, pk=category_id)
|
||||
return self._build_response(category)
|
||||
else:
|
||||
categories = Category.objects.filter(active=True).all()
|
||||
return self._build_multi_response(categories)
|
||||
|
||||
def post(self, request):
|
||||
data = json.loads(request.body)
|
||||
category = Category(
|
||||
name=data.get('name'),
|
||||
description=data.get('description')
|
||||
)
|
||||
|
||||
category.save()
|
||||
return self._build_response(category)
|
||||
|
||||
def put(self, request, category_id):
|
||||
data = json.loads(request.body)
|
||||
|
||||
category = get_object_or_404(Category, pk=category_id)
|
||||
|
||||
category.name = data.get('name', category.name)
|
||||
category.description = data.get('description', category.description)
|
||||
return self._build_response(category)
|
||||
|
||||
def delete(self, request, category_id):
|
||||
category = get_object_or_404(Category, pk=category_id)
|
||||
category.active = False
|
||||
category.save()
|
||||
return self._build_response(category)
|
||||
186
api/views/event.py
Normal file
186
api/views/event.py
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
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=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'),
|
||||
url=data.get('url'),
|
||||
address=data.get('address'),
|
||||
status=data.get('status'),
|
||||
price=data.get('price'),
|
||||
require_rsvp=data.get('require_rsvp'),
|
||||
start_time=data.get('start_time'),
|
||||
end_time=data.get('end_time'),
|
||||
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)
|
||||
event.address = data.get('address', event.address)
|
||||
event.status = data.get('status', event.status)
|
||||
event.price = data.get('price', event.price)
|
||||
event.require_rsvp = data.get('require_rsvp', event.require_rsvp)
|
||||
event.start_time = data.get('start_time', event.start_time)
|
||||
event.end_time = data.get('end_time', event.end_time)
|
||||
event.rain_date = data.get('rain_date', event.rain_date)
|
||||
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
|
||||
event.save()
|
||||
return self._build_response(event)
|
||||
42
api/views/tag.py
Normal file
42
api/views/tag.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import json
|
||||
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from api.serializers.tag import TagSerializer
|
||||
from api.views.base import BaseView
|
||||
from web.models.tag import Tag
|
||||
|
||||
|
||||
class TagView(BaseView):
|
||||
SERIALIZER = TagSerializer
|
||||
|
||||
def get(self, request, tag_id=None):
|
||||
if tag_id:
|
||||
tag = get_object_or_404(Tag, pk=tag_id)
|
||||
return self._build_response(tag)
|
||||
else:
|
||||
tags = Tag.objects.filter(active=True).all()
|
||||
return self._build_multi_response(tags)
|
||||
|
||||
def post(self, request):
|
||||
data = json.loads(request.body)
|
||||
tag = Tag(
|
||||
name=data.get('name'),
|
||||
)
|
||||
|
||||
tag.save()
|
||||
return self._build_response(tag)
|
||||
|
||||
def put(self, request, tag_id):
|
||||
data = json.loads(request.body)
|
||||
|
||||
tag = get_object_or_404(Tag, pk=tag_id)
|
||||
tag.name = data.get('name', tag.name)
|
||||
tag.save()
|
||||
self._build_response(tag)
|
||||
|
||||
def delete(self, request, tag_id):
|
||||
tag = get_object_or_404(Tag, pk=tag_id)
|
||||
tag.active = False
|
||||
tag.save()
|
||||
return self._build_response(tag)
|
||||
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,16 +1,5 @@
|
|||
"""
|
||||
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
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
|
@ -20,7 +9,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
|||
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = "django-insecure-pek1teheggj8zzvtcntp4-u#s_^yc$&a@3f7wi+u%8)g*2xezw"
|
||||
SECRET_KEY = SECRET_KEY
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
|
@ -31,17 +20,23 @@ ALLOWED_HOSTS = []
|
|||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"corsheaders",
|
||||
"django.contrib.gis",
|
||||
"leaflet",
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"rest_framework",
|
||||
"web",
|
||||
"api",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"corsheaders.middleware.CorsMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
|
|
@ -76,8 +71,12 @@ WSGI_APPLICATION = "localist.wsgi.application"
|
|||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": BASE_DIR / "db.sqlite3",
|
||||
"ENGINE": "django.contrib.gis.db.backends.postgis",
|
||||
"NAME": DB_NAME,
|
||||
"USER": DB_USER,
|
||||
"PASSWORD": DB_PASSWORD,
|
||||
"HOST": DB_HOST,
|
||||
"PORT": DB_PORT
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -117,3 +116,9 @@ USE_TZ = True
|
|||
# https://docs.djangoproject.com/en/6.0/howto/static-files/
|
||||
|
||||
STATIC_URL = "static/"
|
||||
|
||||
|
||||
CORS_ALLOWED_ORIGINS = [
|
||||
"http://localhost:5173",
|
||||
"http://127.0.0.1:5173",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,23 +1,16 @@
|
|||
"""
|
||||
URL configuration for localist project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/6.0/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
|
||||
from api.views.event import EventView
|
||||
from api.views.category import CategoryView
|
||||
from api.views.tag import TagView
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
path("api/event/<int:event_id>", EventView.as_view(), name="event"),
|
||||
path("api/event", EventView.as_view(), name="event"),
|
||||
path("api/category/<int:category_id>", CategoryView.as_view(), name="category"),
|
||||
path("api/category", CategoryView.as_view(), name="category"),
|
||||
path("api/tag/<int:tag_id>", TagView.as_view(), name="tag"),
|
||||
path("api/tag", TagView.as_view(), name="tag"),
|
||||
]
|
||||
|
|
|
|||
23
manage.py
Executable file
23
manage.py
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "localist.settings")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
14
requirements.txt
Normal file
14
requirements.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
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
|
||||
psycopg==3.3.4
|
||||
psycopg-binary==3.3.4
|
||||
requests==2.34.2
|
||||
sqlparse==0.5.5
|
||||
urllib3==2.7.0
|
||||
11
test_requests/category_post.py
Normal file
11
test_requests/category_post.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import requests
|
||||
|
||||
url = "http://127.0.0.1:8000/api/category"
|
||||
|
||||
data = {
|
||||
"name": "Test Category",
|
||||
"description": "this is a test",
|
||||
}
|
||||
|
||||
resp = requests.post(url, data=data)
|
||||
print(resp.status_code)
|
||||
10
test_requests/category_put.py
Normal file
10
test_requests/category_put.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import requests
|
||||
|
||||
url = "http://127.0.0.1:8000/api/category/1"
|
||||
|
||||
data = {
|
||||
"name": "diddy cat"
|
||||
}
|
||||
|
||||
resp = requests.put(url, data=data)
|
||||
print(resp.status_code)
|
||||
24
test_requests/event_post.py
Normal file
24
test_requests/event_post.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import requests
|
||||
|
||||
url = "http://127.0.0.1:8000/api/event"
|
||||
|
||||
data = {
|
||||
"name": "Test Event 3",
|
||||
"description": "this is a test",
|
||||
"url": "https://www.domdit.com",
|
||||
"address": "31 Fleetwood Dr Hazlet Nj",
|
||||
"status": "scheduled",
|
||||
"price": "10.00",
|
||||
"require_rsvp": False,
|
||||
"start_time": "2026-06-14T01:34:39Z",
|
||||
"end_time": "2026-08-14T01:34:38Z",
|
||||
"rain_date": "2026-06-15T01:34:42Z",
|
||||
"email": "me@domdit.com",
|
||||
"phone_number": "",
|
||||
"categories": [1],
|
||||
"tags": ["tag1", "tag2", "tag4"]
|
||||
|
||||
}
|
||||
|
||||
resp = requests.post(url, json=data)
|
||||
print(resp.status_code)
|
||||
10
test_requests/event_put.py
Normal file
10
test_requests/event_put.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import requests
|
||||
|
||||
url = "http://127.0.0.1:8000/api/event/1"
|
||||
|
||||
data = {
|
||||
"name": "diddy freakoff",
|
||||
}
|
||||
|
||||
resp = requests.put(url, data=data)
|
||||
print(resp.status_code)
|
||||
0
test_requests/tag_post.py
Normal file
0
test_requests/tag_post.py
Normal file
0
test_requests/tag_put.py
Normal file
0
test_requests/tag_put.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -5,6 +5,7 @@ from web.models.event_category import EventCategory, EventCategoryAdmin
|
|||
from web.models.event_tag import EventTag, EventTagAdmin
|
||||
from web.models.tag import Tag, TagAdmin
|
||||
|
||||
|
||||
admin.site.register(Category, CategoryAdmin)
|
||||
admin.site.register(Event, EventAdmin)
|
||||
admin.site.register(EventCategory, EventCategoryAdmin)
|
||||
|
|
|
|||
23
web/migrations/0002_alter_eventcategory_category.py
Normal file
23
web/migrations/0002_alter_eventcategory_category.py
Normal 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",
|
||||
),
|
||||
),
|
||||
]
|
||||
21
web/migrations/0003_event_coordinates.py
Normal file
21
web/migrations/0003_event_coordinates.py
Normal 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
|
||||
),
|
||||
),
|
||||
]
|
||||
21
web/migrations/0004_alter_event_coordinates.py
Normal file
21
web/migrations/0004_alter_event_coordinates.py
Normal 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
|
||||
),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,8 +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):
|
||||
|
|
@ -15,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()
|
||||
|
|
@ -30,7 +30,7 @@ class Event(BaseModel):
|
|||
db_table = 'events'
|
||||
|
||||
|
||||
class EventAdmin(admin.ModelAdmin):
|
||||
class EventAdmin(LeafletGeoAdmin):
|
||||
search_fields = (
|
||||
'name',
|
||||
'description',
|
||||
|
|
@ -39,6 +39,7 @@ class EventAdmin(admin.ModelAdmin):
|
|||
'rain_date',
|
||||
'url',
|
||||
'address',
|
||||
'coordinates',
|
||||
'status',
|
||||
'price',
|
||||
'require_rsvp',
|
||||
|
|
@ -52,6 +53,7 @@ class EventAdmin(admin.ModelAdmin):
|
|||
'rain_date',
|
||||
'url',
|
||||
'address',
|
||||
'coordinates',
|
||||
'status',
|
||||
'price',
|
||||
'require_rsvp',
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
Loading…
Reference in a new issue