11 Commits

Author SHA1 Message Date
Kenneth SOARES
0fef2e0071 change MonthField from CharField to DateField 2025-09-09 12:35:10 +02:00
Kenneth SOARES
c7231608a9 fix imports 2025-09-08 13:03:56 +02:00
Kenneth SOARES
c1ff8a9684 fix n+1 issue with InvoiceCall 2025-09-08 12:46:43 +02:00
Kenneth SOARES
494e90f614 date validity verification
fixed template formatting
2025-09-08 12:46:09 +02:00
Kenneth SOARES
efa9f35b45 invoice call form 2025-09-08 12:46:09 +02:00
Kenneth SOARES
5f17ecc739 improved Club related queries
formatted migration file
2025-09-08 12:46:08 +02:00
Kenneth SOARES
9fed57de20 rename is_validated field 2025-09-08 12:46:08 +02:00
Kenneth SOARES
e903198384 MonthField for InvoiceCall 2025-09-08 12:46:08 +02:00
Kenneth SOARES
e990b94941 fix checkbox width 2025-09-08 12:46:08 +02:00
Kenneth SOARES
b04fa90d6e added checkbox for invoice calls
formatting

separated logic for get and post

created custom month field

fixed formatting

fixed imports
2025-09-08 12:46:08 +02:00
Kenneth SOARES
e47e6df9f5 refactored invoice view and template 2025-09-08 12:46:08 +02:00
12 changed files with 299 additions and 139 deletions

View File

@@ -560,7 +560,7 @@ class User(AbstractUser):
"""Determine if the object is owned by the user.""" """Determine if the object is owned by the user."""
if hasattr(obj, "is_owned_by") and obj.is_owned_by(self): if hasattr(obj, "is_owned_by") and obj.is_owned_by(self):
return True return True
if hasattr(obj, "owner_group") and self.is_in_group(pk=obj.owner_group_id): if hasattr(obj, "owner_group") and self.is_in_group(pk=obj.owner_group.id):
return True return True
return self.is_root return self.is_root
@@ -569,15 +569,9 @@ class User(AbstractUser):
if hasattr(obj, "can_be_edited_by") and obj.can_be_edited_by(self): if hasattr(obj, "can_be_edited_by") and obj.can_be_edited_by(self):
return True return True
if hasattr(obj, "edit_groups"): if hasattr(obj, "edit_groups"):
if ( for pk in obj.edit_groups.values_list("pk", flat=True):
hasattr(obj, "_prefetched_objects_cache") if self.is_in_group(pk=pk):
and "edit_groups" in obj._prefetched_objects_cache return True
):
pks = [g.id for g in obj.edit_groups.all()]
else:
pks = list(obj.edit_groups.values_list("id", flat=True))
if any(self.is_in_group(pk=pk) for pk in pks):
return True
if isinstance(obj, User) and obj == self: if isinstance(obj, User) and obj == self:
return True return True
return self.is_owner(obj) return self.is_owner(obj)
@@ -587,18 +581,9 @@ class User(AbstractUser):
if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self): if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self):
return True return True
if hasattr(obj, "view_groups"): if hasattr(obj, "view_groups"):
# if "view_groups" has already been prefetched, use for pk in obj.view_groups.values_list("pk", flat=True):
# the prefetch cache, else fetch only the ids, to make if self.is_in_group(pk=pk):
# the query lighter. return True
if (
hasattr(obj, "_prefetched_objects_cache")
and "view_groups" in obj._prefetched_objects_cache
):
pks = [g.id for g in obj.view_groups.all()]
else:
pks = list(obj.view_groups.values_list("id", flat=True))
if any(self.is_in_group(pk=pk) for pk in pks):
return True
return self.can_edit(obj) return self.can_edit(obj)
def can_be_edited_by(self, user): def can_be_edited_by(self, user):
@@ -1399,9 +1384,9 @@ class Page(models.Model):
@cached_property @cached_property
def is_club_page(self): def is_club_page(self):
return ( club_root_page = Page.objects.filter(name=settings.SITH_CLUB_ROOT_PAGE).first()
self.name == settings.SITH_CLUB_ROOT_PAGE return club_root_page is not None and (
or settings.SITH_CLUB_ROOT_PAGE in [p.name for p in self.get_parent_list()] self == club_root_page or club_root_page in self.get_parent_list()
) )
@cached_property @cached_property

View File

@@ -514,6 +514,10 @@ th {
text-align: center; text-align: center;
padding: 5px 10px; padding: 5px 10px;
>input[type="checkbox"] {
padding: unset;
}
>ul { >ul {
margin-top: 0; margin-top: 0;
} }

View File

@@ -5,12 +5,16 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h3>{% trans %}Page list{% endtrans %}</h3> {% if page_list %}
<ul> <h3>{% trans %}Page list{% endtrans %}</h3>
{% for p in page_list %} <ul>
<li><a href="{{ p.get_absolute_url() }}">{{ p.display_name }}</a></li> {% for p in page_list %}
{% endfor %} <li><a href="{{ p.get_absolute_url() }}">{{ p.get_display_name() }}</a></li>
</ul> {% endfor %}
</ul>
{% else %}
{% trans %}There is no page in this website.{% endtrans %}
{% endif %}
{% endblock %} {% endblock %}

View File

@@ -12,10 +12,7 @@
# OR WITHIN THE LOCAL FILE "LICENSE" # OR WITHIN THE LOCAL FILE "LICENSE"
# #
# #
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db.models import F, OuterRef, Subquery
from django.db.models.functions import Coalesce
# This file contains all the views that concern the page model # This file contains all the views that concern the page model
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
@@ -46,20 +43,6 @@ class CanEditPagePropMixin(CanEditPropMixin):
class PageListView(CanViewMixin, ListView): class PageListView(CanViewMixin, ListView):
model = Page model = Page
template_name = "core/page_list.jinja" template_name = "core/page_list.jinja"
queryset = (
Page.objects.annotate(
display_name=Coalesce(
Subquery(
PageRev.objects.filter(page=OuterRef("id"))
.order_by("-date")
.values("title")[:1]
),
F("name"),
)
)
.prefetch_related("view_groups")
.select_related("parent")
)
class PageView(CanViewMixin, DetailView): class PageView(CanViewMixin, DetailView):

View File

@@ -5,6 +5,7 @@ from django.db.models import Q
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from phonenumber_field.widgets import RegionalPhoneNumberWidget from phonenumber_field.widgets import RegionalPhoneNumberWidget
from club.models import Club
from club.widgets.ajax_select import AutoCompleteSelectClub from club.widgets.ajax_select import AutoCompleteSelectClub
from core.models import User from core.models import User
from core.views.forms import NFCTextInput, SelectDate, SelectDateTime from core.views.forms import NFCTextInput, SelectDate, SelectDateTime
@@ -19,6 +20,7 @@ from counter.models import (
Counter, Counter,
Customer, Customer,
Eticket, Eticket,
InvoiceCall,
Product, Product,
Refilling, Refilling,
ReturnableProduct, ReturnableProduct,
@@ -373,3 +375,39 @@ class BaseBasketForm(forms.BaseFormSet):
BasketForm = forms.formset_factory( BasketForm = forms.formset_factory(
ProductForm, formset=BaseBasketForm, absolute_max=None, min_num=1 ProductForm, formset=BaseBasketForm, absolute_max=None, min_num=1
) )
class InvoiceCallForm(forms.Form):
def __init__(self, *args, month, clubs: list[Club] | None = None, **kwargs):
super().__init__(*args, **kwargs)
self.month = month
self.clubs = clubs
if self.clubs is None:
self.clubs = []
invoices = {
i["club_id"]: i["is_validated"]
for i in InvoiceCall.objects.filter(
club__in=self.clubs, month=self.month
).values("club_id", "is_validated")
}
for club in self.clubs:
is_validated = invoices.get(club.id, False)
self.fields[f"club_{club.id}"] = forms.BooleanField(
required=False, initial=is_validated
)
def save(self):
for club in self.clubs:
field_name = f"club_{club.id}"
is_validated = self.cleaned_data.get(field_name, False)
InvoiceCall.objects.update_or_create(
month=self.month, club=club, defaults={"is_validated": is_validated}
)
def get_club_name(self, club_id):
return f"club_{club_id}"

View File

@@ -0,0 +1,45 @@
# Generated by Django 5.2.3 on 2025-09-09 10:24
import django.db.models.deletion
from django.db import migrations, models
import counter.models
class Migration(migrations.Migration):
dependencies = [
("club", "0014_alter_club_options_rename_unix_name_club_slug_name_and_more"),
("counter", "0031_alter_counter_options"),
]
operations = [
migrations.CreateModel(
name="InvoiceCall",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"is_validated",
models.BooleanField(default=False, verbose_name="is validated"),
),
("month", counter.models.MonthField(verbose_name="invoice date")),
(
"club",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="club.club"
),
),
],
options={
"verbose_name": "Invoice call",
"verbose_name_plural": "Invoice calls",
},
),
]

View File

@@ -1362,3 +1362,45 @@ class ReturnableProductBalance(models.Model):
f"return balance of {self.customer} " f"return balance of {self.customer} "
f"for {self.returnable.product_id} : {self.balance}" f"for {self.returnable.product_id} : {self.balance}"
) )
class MonthField(models.DateField):
description = _("Year + month field (day forced to 1)")
default_error_messages = {
"invalid": _(
"%(value)s” value has an invalid date format. It must be "
"in YYYY-MM format."
),
"invalid_date": _(
"%(value)s” value has the correct format (YYYY-MM) "
"but it is an invalid date."
),
}
def to_python(self, value):
if isinstance(value, date):
return value.replace(day=1)
if isinstance(value, str):
try:
year, month = map(int, value.split("-"))
return date(year, month, 1)
except (ValueError, TypeError) as err:
raise ValueError(
self.error_messages["invalid"] % {"value": value}
) from err
return super().to_python(value)
class InvoiceCall(models.Model):
is_validated = models.BooleanField(verbose_name=_("is validated"), default=False)
club = models.ForeignKey(Club, on_delete=models.CASCADE)
month = MonthField(verbose_name=_("invoice date"))
class Meta:
verbose_name = _("Invoice call")
verbose_name_plural = _("Invoice calls")
def __str__(self):
return f"invoice call of {self.month} made by {self.club}"

View File

@@ -15,24 +15,32 @@
</select> </select>
<input type="submit" value="{% trans %}Go{% endtrans %}" /> <input type="submit" value="{% trans %}Go{% endtrans %}" />
</form> </form>
<br>
<p>{% trans %}CB Payments{% endtrans %} : {{ sum_cb }} €</p>
<br>
<table>
<thead>
<td>{% trans %}Club{% endtrans %}</td>
<td>{% trans %}Sum{% endtrans %}</td>
</thead>
<tbody>
{% for i in sums %}
<tr>
<td>{{ i['club__name'] }}</td>
<td>{{ i['selling_sum'] }} €</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
<form method="post" action="">
{% csrf_token %}
<br>
<p>{% trans %}CB Payments{% endtrans %} : {{ sum_cb }} €</p>
<br>
<table>
<thead>
<td>{% trans %}Club{% endtrans %}</td>
<td>{% trans %}Sum{% endtrans %}</td>
<td>{% trans %}Validated{% endtrans %}</td>
</thead>
<tbody>
{% for data in club_data %}
<tr>
<td>{{ data.club.name }}</td>
<td>{{"%.2f"|format(data.sum)}} €</td>
<td>
{{ form[form.get_club_name(data.club.id)] }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<input type="hidden" name="month" value="{{ start_date|date('Y-m') }}">
<button type="submit">{% trans %}Save validation{% endtrans %}</button>
</form>
{% endblock %}

View File

@@ -12,15 +12,17 @@
# OR WITHIN THE LOCAL FILE "LICENSE" # OR WITHIN THE LOCAL FILE "LICENSE"
# #
# #
from datetime import datetime, timedelta from datetime import date, datetime, timedelta
from datetime import timezone as tz from datetime import timezone as tz
from django.db.models import F from django.db.models import Exists, F, OuterRef
from django.shortcuts import redirect
from django.utils import timezone from django.utils import timezone
from django.views.generic import TemplateView from django.views.generic import TemplateView
from counter.fields import CurrencyField from counter.fields import CurrencyField
from counter.models import Refilling, Selling from counter.forms import InvoiceCallForm
from counter.models import Club, InvoiceCall, Refilling, Selling
from counter.views.mixins import CounterAdminMixin, CounterAdminTabsMixin from counter.views.mixins import CounterAdminMixin, CounterAdminTabsMixin
@@ -28,12 +30,30 @@ class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView):
template_name = "counter/invoices_call.jinja" template_name = "counter/invoices_call.jinja"
current_tab = "invoices_call" current_tab = "invoices_call"
def get(self, request, *args, **kwargs):
month_str = request.GET.get("month")
if month_str:
try:
start_date = datetime.strptime(month_str, "%Y-%m").date()
today = timezone.now().date().replace(day=1)
if start_date > today:
return redirect("counter:invoices_call")
except ValueError:
return redirect("counter:invoices_call")
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Add sums to the context.""" """Add sums to the context."""
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
kwargs["months"] = Selling.objects.datetimes("date", "month", order="DESC") kwargs["months"] = Selling.objects.datetimes("date", "month", order="DESC")
if "month" in self.request.GET: month_str = self.request.GET.get("month")
start_date = datetime.strptime(self.request.GET["month"], "%Y-%m")
if month_str:
try:
start_date = datetime.strptime(self.request.GET["month"], "%Y-%m")
except ValueError:
return redirect("counter:invoices_call")
else: else:
start_date = datetime( start_date = datetime(
year=timezone.now().year, year=timezone.now().year,
@@ -46,30 +66,23 @@ class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView):
) )
from django.db.models import Case, Sum, When from django.db.models import Case, Sum, When
kwargs["sum_cb"] = sum( kwargs["sum_cb"] = Refilling.objects.filter(
[ payment_method="CARD",
r.amount is_validated=True,
for r in Refilling.objects.filter( date__gte=start_date,
payment_method="CARD", date__lte=end_date,
is_validated=True, ).aggregate(amount=Sum(F("amount"), default=0))["amount"]
date__gte=start_date,
date__lte=end_date, kwargs["sum_cb"] += Selling.objects.filter(
) payment_method="CARD",
] is_validated=True,
) date__gte=start_date,
kwargs["sum_cb"] += sum( date__lte=end_date,
[ ).aggregate(amount=Sum(F("quantity") * F("unit_price"), default=0))["amount"]
s.quantity * s.unit_price
for s in Selling.objects.filter(
payment_method="CARD",
is_validated=True,
date__gte=start_date,
date__lte=end_date,
)
]
)
kwargs["start_date"] = start_date kwargs["start_date"] = start_date
kwargs["sums"] = (
kwargs["sums"] = list(
Selling.objects.values("club__name") Selling.objects.values("club__name")
.annotate( .annotate(
selling_sum=Sum( selling_sum=Sum(
@@ -86,4 +99,56 @@ class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView):
.exclude(selling_sum=None) .exclude(selling_sum=None)
.order_by("-selling_sum") .order_by("-selling_sum")
) )
club_names = [i["club__name"] for i in kwargs["sums"]]
clubs = Club.objects.filter(name__in=club_names)
invoice_calls = InvoiceCall.objects.filter(month=month_str, club__in=clubs)
invoice_statuses = {ic.club.name: ic.is_validated for ic in invoice_calls}
kwargs["form"] = InvoiceCallForm(clubs=clubs, month=month_str)
kwargs["club_data"] = []
for club in clubs:
selling_sum = next(
(
item["selling_sum"]
for item in kwargs["sums"]
if item["club__name"] == club.name
),
0,
)
kwargs["club_data"].append(
{
"club": club,
"sum": selling_sum,
"validated": invoice_statuses.get(club.name, False),
}
)
return kwargs return kwargs
def post(self, request, *args, **kwargs):
month_str = request.POST.get("month")
if not month_str:
return self.get(request, *args, **kwargs)
try:
start_date = datetime.strptime(month_str, "%Y-%m")
start_date = date(start_date.year, start_date.month, 1)
except ValueError:
return redirect(request.path)
selling_subquery = Selling.objects.filter(
club=OuterRef("pk"),
date__year=start_date.year,
date__month=start_date.month,
)
clubs = Club.objects.filter(Exists(selling_subquery))
form = InvoiceCallForm(request.POST, clubs=clubs, month=month_str)
if form.is_valid():
form.save()
return redirect(f"{request.path}?month={request.POST.get('month', '')}")

View File

@@ -45,9 +45,8 @@ class Command(BaseCommand):
"verbosity level should be between 0 and 2 included", stacklevel=2 "verbosity level should be between 0 and 2 included", stacklevel=2
) )
if options["verbosity"] >= 2: if options["verbosity"] == 2:
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
logging.getLogger("django.db.backends").setLevel(logging.DEBUG)
elif options["verbosity"] == 1: elif options["verbosity"] == 1:
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
else: else:
@@ -60,3 +59,6 @@ class Command(BaseCommand):
Galaxy.objects.filter(state__isnull=True).delete() Galaxy.objects.filter(state__isnull=True).delete()
logger.info("Ruled the galaxy in {} queries.".format(len(connection.queries))) logger.info("Ruled the galaxy in {} queries.".format(len(connection.queries)))
if options["verbosity"] > 2:
for q in connection.queries:
logger.debug(q)

View File

@@ -31,14 +31,13 @@ from collections import defaultdict
from typing import NamedTuple, TypedDict from typing import NamedTuple, TypedDict
from django.db import models from django.db import models
from django.db.models import Count, Exists, F, OuterRef, Q, QuerySet from django.db.models import Count, F, Q, QuerySet
from django.utils.timezone import localdate, now from django.utils.timezone import localdate
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from club.models import Membership from club.models import Membership
from core.models import User from core.models import User
from sas.models import PeoplePictureRelation, Picture from sas.models import PeoplePictureRelation, Picture
from subscription.models import Subscription
class GalaxyStar(models.Model): class GalaxyStar(models.Model):
@@ -199,16 +198,8 @@ class Galaxy(models.Model):
cls, picture_count_threshold: int = DEFAULT_PICTURE_COUNT_THRESHOLD cls, picture_count_threshold: int = DEFAULT_PICTURE_COUNT_THRESHOLD
) -> QuerySet[User]: ) -> QuerySet[User]:
return ( return (
User.objects.filter(is_subscriber_viewable=True) User.objects.exclude(subscriptions=None)
.exclude(subscriptions=None) .annotate(pictures_count=Count("pictures"))
.annotate(
pictures_count=Count("pictures"),
is_active_in_galaxy=Exists(
Subscription.objects.filter(
member=OuterRef("id"), subscription_end__gt=now()
)
),
)
.filter(pictures_count__gt=picture_count_threshold) .filter(pictures_count__gt=picture_count_threshold)
.distinct() .distinct()
) )
@@ -299,9 +290,9 @@ class Galaxy(models.Model):
31/12/2022 (also two years, but with an offset of one year), then their 31/12/2022 (also two years, but with an offset of one year), then their
club score is 365. club score is 365.
""" """
memberships = user.memberships.values("start_date", "end_date", "club_id") memberships = user.memberships.only("start_date", "end_date", "club_id")
result = defaultdict(int) result = defaultdict(int)
today = localdate() now = localdate()
for membership in memberships: for membership in memberships:
# This is a N+1 query, but 92% of galaxy users have less than 10 memberships. # This is a N+1 query, but 92% of galaxy users have less than 10 memberships.
# Only 5 users have more than 30 memberships. # Only 5 users have more than 30 memberships.
@@ -309,23 +300,23 @@ class Galaxy(models.Model):
Membership.objects.exclude(user=user) Membership.objects.exclude(user=user)
.filter( .filter(
Q( # start2 <= start1 <= end2 Q( # start2 <= start1 <= end2
start_date__lte=membership["start_date"], start_date__lte=membership.start_date,
end_date__gte=membership["start_date"], end_date__gte=membership.start_date,
) )
| Q( # start2 <= start1 <= today | Q( # start2 <= start1 <= now
start_date__lte=membership["start_date"], end_date=None start_date__lte=membership.start_date, end_date=None
) )
| Q( # start1 <= start2 <= end2 | Q( # start1 <= start2 <= end2
start_date__gte=membership["start_date"], start_date__gte=membership.start_date,
start_date__lte=membership["end_date"] or today, start_date__lte=membership.end_date or now,
), ),
club_id=membership["club_id"], club_id=membership.club_id,
) )
.only("start_date", "end_date", "user_id") .only("start_date", "end_date", "user_id")
) )
for other in common_memberships: for other in common_memberships:
start = max(membership["start_date"], other.start_date) start = max(membership.start_date, other.start_date)
end = min(membership["end_date"] or today, other.end_date or today) end = min(membership.end_date or now, other.end_date or now)
result[other.user_id] += (end - start).days * cls.CLUBS_POINTS result[other.user_id] += (end - start).days * cls.CLUBS_POINTS
return result return result
@@ -391,22 +382,18 @@ class Galaxy(models.Model):
# this is memory expensive but prevents a lot of db hits, therefore # this is memory expensive but prevents a lot of db hits, therefore
# is far more time efficient # is far more time efficient
rulable_users_qs = self.get_rulable_users(picture_count_threshold) rulable_users = list(self.get_rulable_users(picture_count_threshold))
active_users_count = rulable_users_qs.filter(is_active_in_galaxy=True).count() rulable_users_count = len(rulable_users)
rulable_users = list(rulable_users_qs)
user1_count = 0 user1_count = 0
self.logger.info( self.logger.info(
f" {len(rulable_users)} citizens (with {active_users_count} active ones) " f"{rulable_users_count} citizen have been listed. Starting to rule."
f"have been listed. Starting to rule."
) )
self.logger.info("Creating stars for all citizen") self.logger.info("Creating stars for all citizen")
individual_scores = self.compute_individual_scores() individual_scores = self.compute_individual_scores()
GalaxyStar.objects.bulk_create( GalaxyStar.objects.bulk_create(
[ [
GalaxyStar( GalaxyStar(owner=user, galaxy=self, mass=individual_scores[user.id])
owner_id=user.id, galaxy=self, mass=individual_scores[user.id]
)
for user in rulable_users for user in rulable_users
] ]
) )
@@ -418,9 +405,9 @@ class Galaxy(models.Model):
t_global_start = time.time() t_global_start = time.time()
while len(rulable_users) > 0: while len(rulable_users) > 0:
user1 = rulable_users.pop() user1 = rulable_users.pop()
if not user1.is_active_in_galaxy:
continue
user1_count += 1 user1_count += 1
rulable_users_count2 = len(rulable_users)
star1 = stars[user1.id] star1 = stars[user1.id]
lanes = [] lanes = []
@@ -461,20 +448,17 @@ class Galaxy(models.Model):
self.logger.info("") self.logger.info("")
self.logger.info(f" Ruling of {self} ".center(60, "#")) self.logger.info(f" Ruling of {self} ".center(60, "#"))
self.logger.info( self.logger.info(
f"Progression: {user1_count}/{active_users_count} " f"Progression: {user1_count}/{rulable_users_count} "
f"citizen -- {active_users_count - user1_count} remaining" f"citizen -- {rulable_users_count - user1_count} remaining"
) )
self.logger.info(f"Speed: {global_avg_speed:.2f} citizen per second") self.logger.info(f"Speed: {global_avg_speed:.2f} citizen per second")
eta = len(rulable_users) // global_avg_speed eta = rulable_users_count2 // global_avg_speed
self.logger.info( self.logger.info(
f"ETA: {int(eta // 60 % 60)} minutes {int(eta % 60)} seconds" f"ETA: {int(eta // 60 % 60)} minutes {int(eta % 60)} seconds"
) )
self.logger.info("#" * 60) self.logger.info("#" * 60)
t_global_start = time.time() t_global_start = time.time()
count, _ = self.stars.filter(Q(lanes1=None) & Q(lanes2=None)).delete()
self.logger.info(f"{count} orphan stars have been trimmed.")
# Here, we get the IDs of the old galaxies that we'll need to delete. In normal operation, only one galaxy # Here, we get the IDs of the old galaxies that we'll need to delete. In normal operation, only one galaxy
# should be returned, and we can't delete it yet, as it's the one still displayed by the Sith. # should be returned, and we can't delete it yet, as it's the one still displayed by the Sith.
old_galaxies_pks = list( old_galaxies_pks = list(

View File

@@ -122,7 +122,7 @@ class TestGalaxyModel(TestCase):
self.com, self.com,
] ]
with self.assertNumQueries(38): with self.assertNumQueries(44):
while len(users) > 0: while len(users) > 0:
user1 = users.pop(0) user1 = users.pop(0)
family_scores = Galaxy.compute_user_family_score(user1) family_scores = Galaxy.compute_user_family_score(user1)
@@ -150,7 +150,7 @@ class TestGalaxyModel(TestCase):
that the number of queries to rule the galaxy is stable. that the number of queries to rule the galaxy is stable.
""" """
galaxy = Galaxy.objects.create() galaxy = Galaxy.objects.create()
with self.assertNumQueries(36): with self.assertNumQueries(39):
galaxy.rule(0) # We want everybody here galaxy.rule(0) # We want everybody here