dev #2
20
backlogger/migrations/0005_item_status.py
Normal file
20
backlogger/migrations/0005_item_status.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('backlogger', '0004_item_hltb_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='item',
|
||||
name='status',
|
||||
field=models.CharField(
|
||||
choices=[('active', 'Active'), ('completed', 'Completed'), ('abandoned', 'Abandoned')],
|
||||
default='active',
|
||||
max_length=10,
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -14,9 +14,19 @@ class Item(models.Model):
|
||||
(OTHER, 'Other'),
|
||||
]
|
||||
|
||||
ACTIVE = 'active'
|
||||
COMPLETED = 'completed'
|
||||
ABANDONED = 'abandoned'
|
||||
STATUS_CHOICES = [
|
||||
(ACTIVE, 'Active'),
|
||||
(COMPLETED, 'Completed'),
|
||||
(ABANDONED, 'Abandoned'),
|
||||
]
|
||||
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='items', null=True)
|
||||
|
||||
category = models.CharField(max_length=10, choices=CATEGORY_CHOICES)
|
||||
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default=ACTIVE)
|
||||
name = models.CharField(max_length=200)
|
||||
progress_percent = models.FloatField(default=0.0)
|
||||
favorite = models.BooleanField(default=False)
|
||||
|
||||
@@ -53,6 +53,10 @@
|
||||
.btn-outline { background: transparent; color: #94a3b8; border: 1px solid #334155; }
|
||||
.btn-danger { background: transparent; color: #f87171; border: 1px solid #f87171; padding: 0.3rem 0.65rem; font-size: 0.78rem; }
|
||||
.btn-danger:hover { background: #f87171; color: #0f172a; opacity: 1; }
|
||||
.btn-done { background: transparent; color: #34d399; border: 1px solid #34d399; padding: 0.3rem 0.65rem; font-size: 0.78rem; }
|
||||
.btn-done:hover { background: #34d399; color: #0f172a; opacity: 1; }
|
||||
.btn-abandon { background: transparent; color: #fb923c; border: 1px solid #fb923c; padding: 0.3rem 0.65rem; font-size: 0.78rem; }
|
||||
.btn-abandon:hover { background: #fb923c; color: #0f172a; opacity: 1; }
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
@@ -72,6 +76,7 @@
|
||||
}
|
||||
.tab:hover { color: #e2e8f0; }
|
||||
.tab.active { color: #38bdf8; border-bottom-color: #38bdf8; }
|
||||
.tab-sep { width: 1px; background: #1e293b; margin: 0.5rem 0.25rem; align-self: stretch; }
|
||||
|
||||
.sort-wrap { padding-bottom: 0.75rem; }
|
||||
.sort-wrap select {
|
||||
@@ -175,13 +180,19 @@
|
||||
|
||||
<div class="filter-bar">
|
||||
<div class="tabs">
|
||||
<a href="?sort={{ sort }}" class="tab {% if shelf == 'active' %}active{% endif %}">Active</a>
|
||||
<a href="?shelf=completed&sort={{ sort }}" class="tab {% if shelf == 'completed' %}active{% endif %}">Completed</a>
|
||||
<a href="?shelf=abandoned&sort={{ sort }}" class="tab {% if shelf == 'abandoned' %}active{% endif %}">Abandoned</a>
|
||||
{% if shelf == 'active' %}
|
||||
<span class="tab-sep"></span>
|
||||
<a href="?sort={{ sort }}" class="tab {% if not category %}active{% endif %}">All</a>
|
||||
{% for val, label in categories %}
|
||||
<a href="?category={{ val }}&sort={{ sort }}" class="tab {% if category == val %}active{% endif %}">{{ label }}</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="sort-wrap">
|
||||
<select onchange="location='?category={{ category }}&sort='+this.value">
|
||||
<select onchange="location='?shelf={{ shelf }}&category={{ category }}&sort='+this.value">
|
||||
<option value="fav" {% if sort == 'fav' %}selected{% endif %}>Favorites first</option>
|
||||
<option value="az" {% if sort == 'az' %}selected{% endif %}>A → Z</option>
|
||||
<option value="za" {% if sort == 'za' %}selected{% endif %}>Z → A</option>
|
||||
@@ -229,6 +240,27 @@
|
||||
|
||||
<div class="card-actions">
|
||||
<a href="{% url 'backlogger:edit' item.pk %}" class="btn btn-sm btn-outline">Edit</a>
|
||||
{% if shelf == 'active' %}
|
||||
<form method="post" action="{% url 'backlogger:set_status' item.pk %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="status" value="completed">
|
||||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||||
<button type="submit" class="btn btn-done">Done</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'backlogger:set_status' item.pk %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="status" value="abandoned">
|
||||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||||
<button type="submit" class="btn btn-abandon">Abandon</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form method="post" action="{% url 'backlogger:set_status' item.pk %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="status" value="active">
|
||||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline">Restore</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<form method="post" action="{% url 'backlogger:delete' item.pk %}" onsubmit="return confirm('Delete "{{ item.name }}"?')">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-danger">Delete</button>
|
||||
|
||||
@@ -7,6 +7,7 @@ urlpatterns = [
|
||||
path('add/', views.item_add, name='add'),
|
||||
path('<int:pk>/edit/', views.item_edit, name='edit'),
|
||||
path('<int:pk>/delete/', views.item_delete, name='delete'),
|
||||
path('<int:pk>/status/', views.item_set_status, name='set_status'),
|
||||
path('steam/login/', views.steam_login, name='steam_login'),
|
||||
path('steam/callback/', views.steam_callback, name='steam_callback'),
|
||||
path('steam/import/', views.steam_import, name='steam_import'),
|
||||
|
||||
@@ -35,8 +35,11 @@ SORT_MAP = {
|
||||
def item_list(request):
|
||||
category = request.GET.get('category', '')
|
||||
sort = request.GET.get('sort', 'fav')
|
||||
shelf = request.GET.get('shelf', Item.ACTIVE)
|
||||
if shelf not in (Item.ACTIVE, Item.COMPLETED, Item.ABANDONED):
|
||||
shelf = Item.ACTIVE
|
||||
|
||||
items = Item.objects.filter(user=request.user)
|
||||
items = Item.objects.filter(user=request.user, status=shelf)
|
||||
if category:
|
||||
items = items.filter(category=category)
|
||||
items = items.order_by(*SORT_MAP.get(sort, SORT_MAP['fav']))
|
||||
@@ -45,6 +48,7 @@ def item_list(request):
|
||||
'items': items,
|
||||
'category': category,
|
||||
'sort': sort,
|
||||
'shelf': shelf,
|
||||
'categories': Item.CATEGORY_CHOICES,
|
||||
})
|
||||
|
||||
@@ -79,6 +83,18 @@ def item_edit(request, pk):
|
||||
return render(request, 'backlogger/item_form.html', {'form': form, 'action': 'Edit', 'item': item})
|
||||
|
||||
|
||||
@login_required
|
||||
def item_set_status(request, pk):
|
||||
if request.method == 'POST':
|
||||
item = get_object_or_404(Item, pk=pk, user=request.user)
|
||||
new_status = request.POST.get('status')
|
||||
if new_status in (Item.ACTIVE, Item.COMPLETED, Item.ABANDONED):
|
||||
item.status = new_status
|
||||
item.save(update_fields=['status', 'updated_at'])
|
||||
next_url = request.POST.get('next') or reverse('backlogger:list')
|
||||
return redirect(next_url)
|
||||
|
||||
|
||||
@login_required
|
||||
def item_delete(request, pk):
|
||||
if request.method == 'POST':
|
||||
|
||||
Reference in New Issue
Block a user