diff --git a/auctions/filters.py b/auctions/filters.py
index ffe43fe..0c3c64f 100755
--- a/auctions/filters.py
+++ b/auctions/filters.py
@@ -32,6 +32,37 @@
)
+class AuctionFilter(django_filters.FilterSet):
+ """Filter for the main auctions list"""
+
+ query = django_filters.CharFilter(
+ method="auction_search",
+ label="",
+ widget=TextInput(
+ attrs={
+ "placeholder": "Filter by auction name, or type a number to see nearby auctions",
+ "hx-get": "",
+ "hx-target": "div.table-container",
+ "hx-trigger": "keyup changed delay:300ms",
+ "hx-swap": "outerHTML",
+ "hx-indicator": ".progress",
+ }
+ ),
+ )
+
+ class Meta:
+ model = Auction
+ fields = [] # nothing here so no buttons show up
+
+ def auction_search(self, queryset, name, value):
+ if value == "joined":
+ return queryset.exclude(joined=False).exclude(joined=0)
+ if value.isnumeric():
+ return queryset.filter(distance__lte=int(value))
+ else:
+ return queryset.filter(title__icontains=value)
+
+
class AuctionTOSFilter(django_filters.FilterSet):
"""This filter is used on any admin views that allow adding users to an auction and on lot creation/winner screens"""
diff --git a/auctions/models.py b/auctions/models.py
index 955bfe9..2c7f812 100755
--- a/auctions/models.py
+++ b/auctions/models.py
@@ -1073,12 +1073,12 @@ def template_lot_link(self):
@property
def template_lot_link_first_column(self):
"""Shown on small screens only"""
- return mark_safe(f'
{self.template_lot_link}')
+ return mark_safe(f'
{self.template_lot_link}')
@property
def template_lot_link_seperate_column(self):
"""Shown on big screens only"""
- return mark_safe(f'
{self.template_lot_link} | ')
+ return mark_safe(f'{self.template_lot_link}')
@property
def can_submit_lots(self):
diff --git a/auctions/tables.py b/auctions/tables.py
index 731cf8e..591b75e 100644
--- a/auctions/tables.py
+++ b/auctions/tables.py
@@ -1,8 +1,9 @@
import django_tables2 as tables
from django.urls import reverse
+from django.utils import formats
from django.utils.safestring import mark_safe
-from .models import AuctionTOS, Lot
+from .models import Auction, AuctionTOS, Lot
class AuctionTOSHTMxTable(tables.Table):
@@ -159,6 +160,56 @@ class Meta:
# }
+class AuctionHTMxTable(tables.Table):
+ hide_string = "d-md-table-cell d-none"
+
+ auction = tables.Column(accessor="title", verbose_name="Auction")
+ date = tables.Column(accessor="date_start", verbose_name="Status")
+ lots = tables.Column(
+ accessor="template_lot_link_seperate_column",
+ verbose_name="Lots",
+ orderable=False,
+ attrs={"th": {"class": hide_string}, "cell": {"class": hide_string}},
+ )
+
+ def render_date(self, value, record):
+ localized_date = formats.date_format(record.template_date_timestamp, use_l10n=True)
+ return mark_safe(f"{record.template_status}{localized_date}{record.ended_badge}")
+
+ def render_auction(self, value, record):
+ auction = record
+ result = f"{auction.title}
"
+ if auction.is_last_used:
+ result += " Your last auction"
+ if auction.is_online:
+ result += " Online"
+ if not auction.promote_this_auction:
+ result += " Not promoted"
+ if auction.distance:
+ result += f" {int(auction.distance)} miles from you"
+ if auction.joined:
+ result += " Joined"
+ result += auction.template_lot_link_first_column + auction.template_promo_info
+ return mark_safe(result)
+
+ class Meta:
+ model = Auction
+ template_name = "tables/bootstrap_htmx.html"
+ fields = (
+ "auction",
+ "date",
+ "lots",
+ )
+ row_attrs = {
+ # 'class': lambda record: str(record.table_class),
+ # 'style':'cursor:pointer;',
+ # 'hx-get': lambda record: "/api/lot/" + str(record.pk),
+ # 'hx-target':"#modals-here",
+ # 'hx-trigger':"click",
+ # '_':"on htmx:afterOnLoad wait 10ms then add .show to #modal then add .show to #modal-backdrop"
+ }
+
+
class LotHTMxTableForUsers(tables.Table):
hide_string = "d-md-table-cell d-none"
# seller = tables.Column(accessor='auctiontos_seller', verbose_name="Seller")
diff --git a/auctions/templates/all_auctions.html b/auctions/templates/all_auctions.html
index 998718d..7a28201 100755
--- a/auctions/templates/all_auctions.html
+++ b/auctions/templates/all_auctions.html
@@ -1,64 +1,27 @@
{% extends "base.html" %}
+{% load render_table from django_tables2 %}
{% load crispy_forms_tags %}
-
{% block title %}Auctions
{% endblock %}
{% load static %}
{% block content %}
+Auctions
+This is a list of club auctions which have been created on this site.
+ Create a new auction
+
+
+{% render_table table %}
- Auctions
- This is a listing of club auctions which have been created on this site.
- Create a new auction
-
-
-
- Auction |
- Date |
- Lots |
-
-
-
- {% if last_auction_used %}
-
- {{ last_auction_used.title }} Your last auction
- {% if not last_auction_used.promote_this_auction %}Not promoted{% endif %}
- {{ last_auction_used.template_lot_link_first_column }}
- {{ last_auction_used.template_promo_info }}
- |
-
- {{ last_auction_used.template_status }}
- {{ last_auction_used.template_date_timestamp }}
- {{ last_auction_used.ended_badge }}
- |
- {{ last_auction_used.template_lot_link_seperate_column }}
-
- {% endif %}
- {% for auction in object_list %}
-
- {{ auction.title }} {% if auction.is_online %}Online{% endif %}
- {% if not auction.promote_this_auction %}Not promoted{% endif %}
- {% if not location_message and auction.number_of_locations %}{{ auction.distance | floatformat:0 }} miles from you{% endif %}
- {{ auction.template_lot_link_first_column }}
- {{ auction.template_promo_info }}
- |
-
- {{ auction.template_status }}
- {{ auction.template_date_timestamp }}
- {{ auction.ended_badge }}
- |
- {{ auction.template_lot_link_seperate_column }}
-
- {% endfor %}
-
-
-Note: Auctions you haven't joined won't appear in this list if they:
+Auctions you've joined will always show up here. Other auctions will only be listed here if they:
- - aren't related to the fish hobby
- - are set to "do not promote"
- - are starting more than 90 days from today
- - were created more than 2 years ago
+ - are related to the fish hobby
+ - have "promote this auction" checked
+ - are starting less than 90 days from today
+ - were created less than 2 years ago
- Auctions you've joined will always show up here.
+
{% endblock %}
{% block extra_js %}{% endblock %}
diff --git a/auctions/templates/tables/table_generic.html b/auctions/templates/tables/table_generic.html
index 29f4999..cbefafe 100644
--- a/auctions/templates/tables/table_generic.html
+++ b/auctions/templates/tables/table_generic.html
@@ -1,3 +1,8 @@
{% load render_table from django_tables2 %}
-
+{% if no_results %}
+
+ {{ no_results | safe }}
+
+{% else %}
{% render_table table %}
+{% endif %}
diff --git a/auctions/urls.py b/auctions/urls.py
index 7d47551..b18784d 100755
--- a/auctions/urls.py
+++ b/auctions/urls.py
@@ -133,8 +133,8 @@
),
path("images//delete/", views.ImageDelete.as_view(), name="delete_image"),
path("images//edit", views.ImageUpdateView.as_view(), name="edit_image"),
- path("auctions/", views.allAuctions.as_view(), name="auctions"),
- path("auctions/all/", views.allAuctions.as_view()),
+ path("auctions/", views.AllAuctions.as_view(), name="auctions"),
+ path("auctions/all/", views.AllAuctions.as_view()),
# path('auctions/new/', views.createAuction, name='createAuction'),
path("auctions/new/", login_required(views.AuctionCreateView.as_view())),
path("auctions//edit/", views.AuctionUpdate.as_view(), name="edit_auction"),
diff --git a/auctions/views.py b/auctions/views.py
index 080d809..81e28e9 100755
--- a/auctions/views.py
+++ b/auctions/views.py
@@ -26,6 +26,7 @@
from django.core.files.base import ContentFile
from django.db.models import (
Avg,
+ Case,
Count,
Exists,
ExpressionWrapper,
@@ -36,6 +37,8 @@
Q,
Subquery,
Sum,
+ Value,
+ When,
)
from django.db.models.base import Model as Model
from django.db.models.functions import TruncDay
@@ -83,6 +86,7 @@
from webpush.models import PushInformation
from .filters import (
+ AuctionFilter,
AuctionTOSFilter,
LotAdminFilter,
LotFilter,
@@ -151,7 +155,7 @@
median_value,
nearby_auctions,
)
-from .tables import AuctionTOSHTMxTable, LotHTMxTable, LotHTMxTableForUsers
+from .tables import AuctionHTMxTable, AuctionTOSHTMxTable, LotHTMxTable, LotHTMxTableForUsers
logger = logging.getLogger(__name__)
@@ -320,6 +324,39 @@ def is_auction_admin(self):
return result
+class LocationMixin:
+ """For location aware views, adds a `get_coordinates()` function which returns a tuple of `latitude, longitude` based on self.request.cookies or userdata
+
+ get_coordinates() should be called before get_context_data
+ make sure to set `view.no_location_message`"""
+
+ # override this message in your view, it'll be shown to users without a location
+ no_location_message = "Click here to set your location"
+
+ # don't set this, it'll get set automatically by get_coordinates() if the user does not have a cookie
+ _location_message = None
+
+ def get_coordinates(self):
+ try:
+ latitude = float(self.request.COOKIES.get("latitude", 0))
+ longitude = float(self.request.COOKIES.get("longitude", 0))
+ except (ValueError, TypeError):
+ latitude, longitude = 0, 0
+
+ if latitude == 0 and longitude == 0:
+ self._location_message = self.no_location_message
+
+ if self.request.user.is_authenticated:
+ latitude = self.request.user.userdata.latitude
+ longitude = self.request.user.userdata.longitude
+ return latitude, longitude
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context["location_message"] = self._location_message
+ return context
+
+
class ClickAd(RedirectView):
def get_redirect_url(self, *args, **kwargs):
try:
@@ -4141,13 +4178,35 @@ def toAccount(request):
return redirect(reverse("userpage", kwargs={"slug": request.user.username}))
-class allAuctions(ListView):
+class AllAuctions(LocationMixin, SingleTableMixin, FilterView):
model = Auction
- template_name = "all_auctions.html"
- ordering = ["-date_end"]
+ no_location_message = "Set your location to see how far away auctions are"
+ table_class = AuctionHTMxTable
+ filterset_class = AuctionFilter
+ paginate_by = 100
+
+ def get_template_names(self):
+ if self.request.htmx:
+ template_name = "tables/table_generic.html"
+ else:
+ template_name = "all_auctions.html"
+ return template_name
def get_queryset(self):
- qs = Auction.objects.exclude(is_deleted=True).order_by("-date_start")
+ last_auction_pk = -1
+ if self.request.user.is_authenticated and self.request.user.userdata.last_auction_used:
+ last_auction_pk = self.request.user.userdata.last_auction_used.pk
+ qs = (
+ Auction.objects.exclude(is_deleted=True)
+ .annotate(
+ is_last_used=Case(
+ When(pk=last_auction_pk, then=Value(1)),
+ default=Value(0),
+ output_field=IntegerField(),
+ )
+ )
+ .order_by("-is_last_used", "-date_start")
+ )
next_90_days = timezone.now() + timedelta(days=90)
two_years_ago = timezone.now() - timedelta(days=365 * 2)
standard_filter = Q(
@@ -4155,19 +4214,7 @@ def get_queryset(self):
date_start__lte=next_90_days,
date_posted__gte=two_years_ago,
)
- latitude = 0
- longitude = 0
- try:
- latitude = self.request.COOKIES["latitude"]
- longitude = self.request.COOKIES["longitude"]
- except:
- if self.request.user.is_authenticated:
- userData, created = UserData.objects.get_or_create(
- user=self.request.user,
- defaults={},
- )
- latitude = userData.latitude
- longitude = userData.longitude
+ latitude, longitude = self.get_coordinates()
if latitude and longitude:
closest_pickup_location_subquery = (
PickupLocation.objects.filter(auction=OuterRef("pk"))
@@ -4176,33 +4223,36 @@ def get_queryset(self):
.values("distance")[:1]
)
qs = qs.annotate(distance=Subquery(closest_pickup_location_subquery))
- if self.request.user.is_superuser:
- return qs.exclude(pk=self.request.user.userdata.last_auction_used.pk)
+ else:
+ qs = qs.annotate(distance=Value(0, output_field=FloatField()))
if not self.request.user.is_authenticated:
- return qs.filter(standard_filter)
- qs = qs.filter(
- Q(auctiontos__user=self.request.user)
- | Q(auctiontos__email=self.request.user.email)
- | Q(created_by=self.request.user)
- | standard_filter
- ).distinct()
- if self.request.user.userdata.last_auction_used:
- # union messes with ordering
- qs = qs.exclude(pk=self.request.user.userdata.last_auction_used.pk)
+ return qs.filter(standard_filter).annotate(joined=Value(0, output_field=FloatField())).distinct()
+ qs = (
+ qs.filter(
+ Q(auctiontos__user=self.request.user)
+ | Q(auctiontos__email=self.request.user.email)
+ | Q(created_by=self.request.user)
+ | standard_filter
+ )
+ .annotate(
+ joined=Exists(
+ AuctionTOS.objects.filter(
+ auction=OuterRef("pk"),
+ user=self.request.user,
+ )
+ )
+ )
+ .distinct()
+ )
return qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- try:
- self.request.COOKIES["longitude"]
- except:
- if self.request.user.is_authenticated:
- context["location_message"] = "Set your location to get notifications about new auctions near you"
- else:
- context["location_message"] = "Set your location to see how far away auctions are"
context["hide_google_login"] = True
- if self.request.user.is_authenticated:
- context["last_auction_used"] = self.request.user.userdata.last_auction_used
+ if not self.object_list.exists():
+ context["no_results"] = (
+ "No auctions found. This only searches club auctions, if you're looking for fish to buy, check out the list of lots for sale"
+ )
return context