mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-21 22:09:36 +02:00
feat: Add achievements app with models, admin, and management command for seeding data
This commit is contained in:
parent
e93909e7ea
commit
a00d2abe0d
12 changed files with 160 additions and 0 deletions
0
backend/server/achievements/__init__.py
Normal file
0
backend/server/achievements/__init__.py
Normal file
9
backend/server/achievements/admin.py
Normal file
9
backend/server/achievements/admin.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from allauth.account.decorators import secure_admin_login
|
||||||
|
from achievements.models import Achievement, UserAchievement
|
||||||
|
|
||||||
|
admin.autodiscover()
|
||||||
|
admin.site.login = secure_admin_login(admin.site.login)
|
||||||
|
|
||||||
|
admin.site.register(Achievement)
|
||||||
|
admin.site.register(UserAchievement)
|
6
backend/server/achievements/apps.py
Normal file
6
backend/server/achievements/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AchievementsConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'achievements'
|
0
backend/server/achievements/management/__init__.py
Normal file
0
backend/server/achievements/management/__init__.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import json
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from achievements.models import Achievement
|
||||||
|
|
||||||
|
US_STATE_CODES = [
|
||||||
|
'US-AL', 'US-AK', 'US-AZ', 'US-AR', 'US-CA', 'US-CO', 'US-CT', 'US-DE',
|
||||||
|
'US-FL', 'US-GA', 'US-HI', 'US-ID', 'US-IL', 'US-IN', 'US-IA', 'US-KS',
|
||||||
|
'US-KY', 'US-LA', 'US-ME', 'US-MD', 'US-MA', 'US-MI', 'US-MN', 'US-MS',
|
||||||
|
'US-MO', 'US-MT', 'US-NE', 'US-NV', 'US-NH', 'US-NJ', 'US-NM', 'US-NY',
|
||||||
|
'US-NC', 'US-ND', 'US-OH', 'US-OK', 'US-OR', 'US-PA', 'US-RI', 'US-SC',
|
||||||
|
'US-SD', 'US-TN', 'US-TX', 'US-UT', 'US-VT', 'US-VA', 'US-WA', 'US-WV',
|
||||||
|
'US-WI', 'US-WY'
|
||||||
|
]
|
||||||
|
|
||||||
|
ACHIEVEMENTS = [
|
||||||
|
{
|
||||||
|
"name": "First Adventure",
|
||||||
|
"key": "achievements.first_adventure",
|
||||||
|
"type": "adventure_count",
|
||||||
|
"description": "Log your first adventure!",
|
||||||
|
"condition": {"type": "adventure_count", "value": 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Explorer",
|
||||||
|
"key": "achievements.explorer",
|
||||||
|
"type": "adventure_count",
|
||||||
|
"description": "Log 10 adventures.",
|
||||||
|
"condition": {"type": "adventure_count", "value": 10},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Globetrotter",
|
||||||
|
"key": "achievements.globetrotter",
|
||||||
|
"type": "country_count",
|
||||||
|
"description": "Visit 5 different countries.",
|
||||||
|
"condition": {"type": "country_count", "value": 5},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "American Dream",
|
||||||
|
"key": "achievements.american_dream",
|
||||||
|
"type": "country_count",
|
||||||
|
"description": "Visit all 50 states in the USA.",
|
||||||
|
"condition": {"type": "country_count", "items": US_STATE_CODES},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Seeds the database with predefined achievements"
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
for achievement_data in ACHIEVEMENTS:
|
||||||
|
achievement, created = Achievement.objects.update_or_create(
|
||||||
|
name=achievement_data["name"],
|
||||||
|
defaults={
|
||||||
|
"description": achievement_data["description"],
|
||||||
|
"condition": json.dumps(achievement_data["condition"]),
|
||||||
|
"type": achievement_data["type"],
|
||||||
|
"key": achievement_data["key"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"✅ Created: {achievement.name}"))
|
||||||
|
else:
|
||||||
|
self.stdout.write(self.style.WARNING(f"🔄 Updated: {achievement.name}"))
|
39
backend/server/achievements/migrations/0001_initial.py
Normal file
39
backend/server/achievements/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Generated by Django 5.0.8 on 2025-02-04 04:34
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Achievement',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255, unique=True)),
|
||||||
|
('description', models.TextField()),
|
||||||
|
('icon', models.ImageField(blank=True, null=True, upload_to='achievements/')),
|
||||||
|
('condition', models.JSONField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserAchievement',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('earned_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('achievement', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='achievements.achievement')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('user', 'achievement')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
0
backend/server/achievements/migrations/__init__.py
Normal file
0
backend/server/achievements/migrations/__init__.py
Normal file
33
backend/server/achievements/models.py
Normal file
33
backend/server/achievements/models.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
VALID_ACHIEVEMENT_TYPES = [
|
||||||
|
"adventure_count",
|
||||||
|
"country_count",
|
||||||
|
]
|
||||||
|
|
||||||
|
class Achievement(models.Model):
|
||||||
|
"""Stores all possible achievements"""
|
||||||
|
name = models.CharField(max_length=255, unique=True)
|
||||||
|
key = models.CharField(max_length=255, unique=True) # Used for frontend lookups, e.g. "achievements.first_adventure"
|
||||||
|
type = models.CharField(max_length=255) # adventure_count, country_count, etc.
|
||||||
|
description = models.TextField()
|
||||||
|
icon = models.ImageField(upload_to="achievements/", null=True, blank=True)
|
||||||
|
condition = models.JSONField() # Stores rules like {"type": "adventure_count", "value": 10}
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class UserAchievement(models.Model):
|
||||||
|
"""Tracks which achievements a user has earned"""
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
achievement = models.ForeignKey(Achievement, on_delete=models.CASCADE)
|
||||||
|
earned_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ("user", "achievement") # Prevent duplicates
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user.username} - {self.achievement.name}"
|
3
backend/server/achievements/tests.py
Normal file
3
backend/server/achievements/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
3
backend/server/achievements/views.py
Normal file
3
backend/server/achievements/views.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
|
@ -61,6 +61,7 @@ INSTALLED_APPS = (
|
||||||
'users',
|
'users',
|
||||||
'integrations',
|
'integrations',
|
||||||
'django.contrib.gis',
|
'django.contrib.gis',
|
||||||
|
'achievements',
|
||||||
# 'widget_tweaks',
|
# 'widget_tweaks',
|
||||||
# 'slippers',
|
# 'slippers',
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue