Add user-related collections feature with HTMX support and timestamps for collections
This commit is contained in:
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-01-26 11:05
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('anatomy', '0024_exam_results_supervisor_visible'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='created',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, help_text='Creation timestamp'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, help_text='Last updated timestamp'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-01-26 11:05
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('atlas', '0089_remove_condition_primary_remove_condition_synonym'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='created',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, help_text='Creation timestamp'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='casecollection',
|
||||||
|
name='updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, help_text='Last updated timestamp'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -20,7 +20,14 @@
|
|||||||
hx-swap="innerHTML">
|
hx-swap="innerHTML">
|
||||||
Collections to view / take
|
Collections to view / take
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-outline-primary ms-2"
|
||||||
|
hx-get="{% url 'atlas:user_related_collections' %}"
|
||||||
|
hx-target="#user-related-collections-placeholder"
|
||||||
|
hx-swap="innerHTML">
|
||||||
|
My collections
|
||||||
|
</button>
|
||||||
<div id="user-collections-placeholder" class="mt-3"></div>
|
<div id="user-collections-placeholder" class="mt-3"></div>
|
||||||
|
<div id="user-related-collections-placeholder" class="mt-3"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Related collections</h5>
|
||||||
|
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
{% for item in related_collections %}
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'atlas:collection_detail' item.collection.pk %}">{{ item.collection.name }}</a>
|
||||||
|
{% if item.is_author %}<span class="badge bg-primary ms-2">Author</span>{% endif %}
|
||||||
|
{% if item.is_marker %}<span class="badge bg-secondary ms-2">Marker</span>{% endif %}
|
||||||
|
{% if item.collection.description %}<small class="text-muted"> — {{ item.collection.description }}</small>{% endif %}
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li class="text-muted">No related collections.</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -37,6 +37,7 @@ urlpatterns = [
|
|||||||
path("collection/case_search", views.case_search, name="case_search"),
|
path("collection/case_search", views.case_search, name="case_search"),
|
||||||
path("collection/", views.CollectionView.as_view(), name="collection_view"),
|
path("collection/", views.CollectionView.as_view(), name="collection_view"),
|
||||||
path("collection/user", views.user_collections, name="user_collections"),
|
path("collection/user", views.user_collections, name="user_collections"),
|
||||||
|
path("collection/related", views.user_related_collections, name="user_related_collections"),
|
||||||
path("uploads", views.user_uploads, name="user_uploads"),
|
path("uploads", views.user_uploads, name="user_uploads"),
|
||||||
path("uploads/<int:user_pk>/user", views.other_user_uploads, name="other_user_uploads"),
|
path("uploads/<int:user_pk>/user", views.other_user_uploads, name="other_user_uploads"),
|
||||||
path("uploads/all", views.all_uploads, name="all_uploads"),
|
path("uploads/all", views.all_uploads, name="all_uploads"),
|
||||||
|
|||||||
@@ -1151,6 +1151,39 @@ def user_collections(request):
|
|||||||
# endpoint is called by HTMX we prefer returning a small fragment so it
|
# endpoint is called by HTMX we prefer returning a small fragment so it
|
||||||
# can be swapped into the index page without duplicating layout.
|
# can be swapped into the index page without duplicating layout.
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def user_related_collections(request):
|
||||||
|
"""Return up to 10 most recently modified collections where the user
|
||||||
|
is an author or a marker. If called via HTMX return a fragment suitable
|
||||||
|
for swapping into the index page.
|
||||||
|
"""
|
||||||
|
user = request.user
|
||||||
|
qs = (
|
||||||
|
CaseCollection.objects.filter(Q(author=user) | Q(markers=user))
|
||||||
|
.distinct()
|
||||||
|
.order_by("-updated")[:10]
|
||||||
|
)
|
||||||
|
|
||||||
|
related = []
|
||||||
|
for c in qs:
|
||||||
|
related.append(
|
||||||
|
{
|
||||||
|
"collection": c,
|
||||||
|
"is_author": c.author.filter(id=user.id).exists(),
|
||||||
|
"is_marker": c.markers.filter(id=user.id).exists(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
context = {"related_collections": related}
|
||||||
|
|
||||||
|
is_htmx = getattr(request, "htmx", False) or request.headers.get("HX-Request", "").lower() == "true"
|
||||||
|
if is_htmx:
|
||||||
|
html = render_to_string("atlas/partials/_user_related_collections.html", context, request=request)
|
||||||
|
return HttpResponse(html)
|
||||||
|
|
||||||
|
return render(request, "atlas/user_collections.html", context)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def collection_options(request):
|
def collection_options(request):
|
||||||
if not request.htmx:
|
if not request.htmx:
|
||||||
|
|||||||
@@ -667,6 +667,9 @@ class ExamOrCollectionGenericBase(models.Model, AuthorMixin):
|
|||||||
|
|
||||||
name = models.CharField(max_length=200, help_text="Name of the exam/collection")
|
name = models.CharField(max_length=200, help_text="Name of the exam/collection")
|
||||||
|
|
||||||
|
created = models.DateTimeField(auto_now_add=True, help_text="Creation timestamp")
|
||||||
|
updated = models.DateTimeField(auto_now=True, help_text="Last updated timestamp")
|
||||||
|
|
||||||
start_date = models.DateTimeField(
|
start_date = models.DateTimeField(
|
||||||
help_text="Date (and time) the exam/collection starts", null=True, blank=True
|
help_text="Date (and time) the exam/collection starts", null=True, blank=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-01-26 11:05
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('longs', '0033_alter_longseriesimage_image_md5_hash'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='created',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, help_text='Creation timestamp'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, help_text='Last updated timestamp'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-01-26 11:05
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('physics', '0017_exam_results_supervisor_visible'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='created',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, help_text='Creation timestamp'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, help_text='Last updated timestamp'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-01-26 11:05
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('rapids', '0016_exam_results_supervisor_visible'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='created',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, help_text='Creation timestamp'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, help_text='Last updated timestamp'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-01-26 11:05
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('sbas', '0022_question_frcr_appropriate'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='created',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, help_text='Creation timestamp'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, help_text='Last updated timestamp'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-01-26 11:05
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('shorts', '0010_alter_useranswer_candidate_feedback_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='created',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, help_text='Creation timestamp'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='exam',
|
||||||
|
name='updated',
|
||||||
|
field=models.DateTimeField(auto_now=True, help_text='Last updated timestamp'),
|
||||||
|
),
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user