Compare commits

..

3 Commits

Author SHA1 Message Date
Sli
917a2b50cc Fix naming, fix tooltip and cosmetic changes 2025-10-31 21:51:12 +01:00
imperosol
118a08372f simplify poster moderation 2025-10-31 17:16:52 +01:00
Sli
b8429a510f posters: fix broken moderation view 2025-10-31 12:15:44 +01:00
11 changed files with 176 additions and 239 deletions

View File

@@ -59,7 +59,7 @@ from com.views import (
PosterEditBaseView, PosterEditBaseView,
PosterListBaseView, PosterListBaseView,
) )
from core.auth.mixins import CanEditMixin from core.auth.mixins import CanEditMixin, PermissionOrClubBoardRequiredMixin
from core.models import PageRev from core.models import PageRev
from core.views import DetailFormView, PageEditViewBase, UseFragmentsMixin from core.views import DetailFormView, PageEditViewBase, UseFragmentsMixin
from core.views.mixins import FragmentMixin, FragmentRenderer, TabedViewMixin from core.views.mixins import FragmentMixin, FragmentRenderer, TabedViewMixin
@@ -758,11 +758,13 @@ class MailingAutoGenerationView(View):
return redirect("club:mailing", club_id=club.id) return redirect("club:mailing", club_id=club.id)
class PosterListView(ClubTabsMixin, PosterListBaseView): class PosterListView(
PermissionOrClubBoardRequiredMixin, ClubTabsMixin, PosterListBaseView
):
"""List communication posters.""" """List communication posters."""
current_tab = "posters" current_tab = "posters"
extra_context = {"app": "club"} permission_required = "com.view_poster"
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(club=self.club.id) return super().get_queryset().filter(club=self.club.id)
@@ -770,6 +772,17 @@ class PosterListView(ClubTabsMixin, PosterListBaseView):
def get_object(self): def get_object(self):
return self.club return self.club
def get_context_data(self, **kwargs):
return super().get_context_data(**kwargs) | {
"create_url": reverse_lazy(
"club:poster_create", kwargs={"club_id": self.club.id}
),
"get_edit_url": lambda poster: reverse(
"club:poster_edit",
kwargs={"club_id": self.club.id, "poster_id": poster.id},
),
}
class PosterCreateView(ClubTabsMixin, PosterCreateBaseView): class PosterCreateView(ClubTabsMixin, PosterCreateBaseView):
"""Create communication poster.""" """Create communication poster."""

View File

@@ -402,9 +402,7 @@ class Poster(models.Model):
groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID] groups__id__in=[settings.SITH_GROUP_COM_ADMIN_ID]
): ):
Notification.objects.create( Notification.objects.create(
user=user, user=user, url=reverse("com:poster_list"), type="POSTER_MODERATION"
url=reverse("com:poster_moderate_list"),
type="POSTER_MODERATION",
) )
return super().save(*args, **kwargs) return super().save(*args, **kwargs)

View File

@@ -20,33 +20,7 @@
position: absolute; position: absolute;
display: flex; display: flex;
bottom: 5px; bottom: 5px;
left: 0;
&.left {
left: 0;
}
&.right {
right: 0;
}
.link {
padding: 5px;
padding-left: 20px;
padding-right: 20px;
margin-left: 5px;
border-radius: 20px;
background-color: hsl(40, 100%, 50%);
color: black;
&:hover {
color: black;
background-color: hsl(40, 58%, 50%);
}
&.delete {
background-color: hsl(0, 100%, 40%);
}
}
} }
} }
@@ -143,43 +117,15 @@
} }
} }
.edit, .actions {
.moderate, display: flex;
.slideshow { flex-direction: column;
padding: 5px; align-items: stretch;
border-radius: 20px; form {
background-color: hsl(40, 100%, 50%); margin: unset;
color: black; padding: unset;
button {
&:hover { width: 100%;
color: black;
background-color: hsl(40, 58%, 50%);
}
&:nth-child(2n) {
margin-top: 5px;
margin-bottom: 5px;
}
}
.tooltip {
visibility: hidden;
width: 120px;
background-color: hsl(210, 20%, 98%);
color: hsl(0, 0%, 0%);
text-align: center;
padding: 5px 0;
border-radius: 6px;
position: absolute;
z-index: 10;
ul {
margin-left: 0;
display: inline-block;
li {
display: list-item;
list-style-type: none;
} }
} }
} }

View File

@@ -13,53 +13,53 @@
<div id="title"> <div id="title">
<h3>{% trans %}Posters{% endtrans %}</h3> <h3>{% trans %}Posters{% endtrans %}</h3>
<div id="links" class="right"> <div id="links">
{% if app == "com" %} <a id="create" class="btn btn-blue" href="{{ create_url }}">
<a id="create" class="link" href="{{ url(app + ":poster_create") }}">{% trans %}Create{% endtrans %}</a> <i class="fa fa-plus"></i>
<a id="moderation" class="link" href="{{ url("com:poster_moderate_list") }}">{% trans %}Moderation{% endtrans %}</a> {% trans %}Create{% endtrans %}
{% elif app == "club" %} </a>
<a id="create" class="link" href="{{ url(app + ":poster_create", club.id) }}">{% trans %}Create{% endtrans %}</a>
{% endif %}
</div> </div>
</div> </div>
<div id="posters"> <div id="posters">
{% for poster in poster_list %}
{% if poster_list.count() == 0 %} <div class="poster{% if not poster.is_moderated %} not_moderated{% endif %}">
<div id="no-posters">{% trans %}No posters{% endtrans %}</div> <div class="name">{{ poster.name }}</div>
{% else %} <div
class="image"
{% for poster in poster_list %} hover="{% trans %}Click to expand{% endtrans %}"
<div class="poster{% if not poster.is_moderated %} not_moderated{% endif %}"> @click="active = $el.firstElementChild"
<div class="name">{{ poster.name }}</div> tooltip="{%- for screen in poster.screens.all() -%}
<div {{ screen }}
class="image" {% endfor %}"
hover="{% trans %}Click to expand{% endtrans %}" >
@click="active = $el.firstElementChild" <img src="{{ poster.file.url }}" alt="{{ poster.name }}">
>
<img src="{{ poster.file.url }}"></img>
</div>
<div class="dates">
<div class="begin">{{ poster.date_begin | localtime | date("d/M/Y H:m") }}</div>
<div class="end">{{ poster.date_end | localtime | date("d/M/Y H:m") }}</div>
</div>
{% if app == "com" %}
<a class="edit" href="{{ url(app + ":poster_edit", poster.id) }}">{% trans %}Edit{% endtrans %}</a>
{% elif app == "club" %}
<a class="edit" href="{{ url(app + ":poster_edit", club.id, poster.id) }}">{% trans %}Edit{% endtrans %}</a>
{% endif %}
<div class="tooltip">
<ul>
{% for screen in poster.screens.all() %}
<li>{{ screen }}</li>
{% endfor %}
</ul>
</div>
</div> </div>
{% endfor %} <div class="dates">
<div class="begin">{{ poster.date_begin | localtime | date("d/M/Y H:m") }}</div>
{% endif %} <div class="end">{{ poster.date_end | localtime | date("d/M/Y H:m") }}</div>
</div>
<div class="actions">
{% if poster.is_editable %}
<a class="btn btn-blue" href="{{ get_edit_url(poster) }}">
<i class="fa fa-pen-to-square"></i>
{% trans %}Edit{% endtrans %}
</a>
{% endif %}
{% if not poster.is_moderated and user.has_perm("com.moderate_poster") %}
<form action="{{ url("com:poster_moderate", object_id=poster.id) }}" method="post">
{% csrf_token %}
<button type="submit" class="btn btn-green">
<i class="fa fa-check"></i>
{% trans %}Moderate{% endtrans %}
</button>
</form>
{% endif %}
</div>
</div>
{% else %}
<div id="no-posters">{% trans %}No posters{% endtrans %}</div>
{% endfor %}
</div> </div>
<div <div
@@ -68,7 +68,9 @@
@click="active = null" @click="active = null"
:class="{active: active !== null}" :class="{active: active !== null}"
> >
<div id="placeholder"><img :src="active?.src"></div> <div id="placeholder">
<img :src="active?.src" :alt="active?.name">
</div>
</div> </div>
</div> </div>

View File

@@ -1,43 +0,0 @@
{% extends "core/base.jinja" %}
{% block script %}
{{ super() }}
<script src="{{ static('com/js/poster_list.js') }}"></script>
{% endblock %}
{% block additional_css %}
<link rel="stylesheet" href="{{ static('com/css/posters.scss') }}">
{% endblock %}
{% block content %}
<div id="poster_list">
<div id="title">
<div id="links" class="left">
<a id="list" class="link" href="{{ url("com:poster_list") }}">{% trans %}List{% endtrans %}</a>
</div>
<h3>{% trans %}Posters - moderation{% endtrans %}</h3>
</div>
<div id="posters">
{% if object_list.count == 0 %}
<div id="no-posters">{% trans %}No objects{% endtrans %}</div>
{% else %}
{% for poster in object_list %}
<div class="poster{% if not poster.is_moderated %} not_moderated{% endif %}">
<div class="name"> {{ poster.name }} </div>
<div class="image"> <img src="{{ poster.file.url }}"></img> </div>
<a class="moderate" href="{{ url("com:poster_moderate", object_id=poster.id) }}">Moderate</a>
</div>
{% endfor %}
{% endif %}
</div>
<div id="view"><div id="placeholder"></div></div>
</div>
{% endblock %}

View File

@@ -17,7 +17,9 @@ from unittest.mock import patch
import pytest import pytest
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Permission
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import Client, TestCase from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
from django.utils import html from django.utils import html
@@ -27,9 +29,10 @@ from model_bakery import baker
from pytest_django.asserts import assertNumQueries, assertRedirects from pytest_django.asserts import assertNumQueries, assertRedirects
from club.models import Club, Membership from club.models import Club, Membership
from com.models import News, NewsDate, Sith, Weekmail, WeekmailArticle from com.models import News, NewsDate, Poster, Sith, Weekmail, WeekmailArticle
from core.baker_recipes import subscriber_user from core.baker_recipes import subscriber_user
from core.models import AnonymousUser, Group, User from core.models import AnonymousUser, Group, User
from core.utils import RED_PIXEL_PNG
@pytest.fixture() @pytest.fixture()
@@ -314,7 +317,6 @@ def test_feed(client: Client):
[ [
reverse("com:poster_list"), reverse("com:poster_list"),
reverse("com:poster_create"), reverse("com:poster_create"),
reverse("com:poster_moderate_list"),
], ],
) )
def test_poster_management_views_crash_test(client: Client, url: str): def test_poster_management_views_crash_test(client: Client, url: str):
@@ -325,3 +327,37 @@ def test_poster_management_views_crash_test(client: Client, url: str):
client.force_login(user) client.force_login(user)
res = client.get(url) res = client.get(url)
assert res.status_code == 200 assert res.status_code == 200
@pytest.mark.django_db
@pytest.mark.parametrize(
"referer",
[
None,
reverse("com:poster_list"),
reverse("club:poster_list", kwargs={"club_id": settings.SITH_MAIN_CLUB_ID}),
],
)
def test_moderate_poster(client: Client, referer: str | None):
poster = baker.make(
Poster,
is_moderated=False,
file=SimpleUploadedFile("test.png", content=RED_PIXEL_PNG),
club_id=settings.SITH_MAIN_CLUB_ID,
)
user = baker.make(
User,
user_permissions=Permission.objects.filter(
codename__in=["view_poster", "moderate_poster"]
),
)
client.force_login(user)
headers = {"REFERER": f"https://{settings.SITH_URL}{referer}"} if referer else {}
response = client.post(
reverse("com:poster_moderate", kwargs={"object_id": poster.id}), headers=headers
)
result_url = referer or reverse("com:poster_list")
assertRedirects(response, result_url)
poster.refresh_from_db()
assert poster.is_moderated
assert poster.moderator == user

View File

@@ -33,7 +33,6 @@ from com.views import (
PosterDeleteView, PosterDeleteView,
PosterEditView, PosterEditView,
PosterListView, PosterListView,
PosterModerateListView,
PosterModerateView, PosterModerateView,
ScreenCreateView, ScreenCreateView,
ScreenDeleteView, ScreenDeleteView,
@@ -102,11 +101,6 @@ urlpatterns = [
PosterDeleteView.as_view(), PosterDeleteView.as_view(),
name="poster_delete", name="poster_delete",
), ),
path(
"poster/moderate/",
PosterModerateListView.as_view(),
name="poster_moderate_list",
),
path( path(
"poster/<int:object_id>/moderate/", "poster/<int:object_id>/moderate/",
PosterModerateView.as_view(), PosterModerateView.as_view(),

View File

@@ -25,6 +25,7 @@ import itertools
from datetime import date, timedelta from datetime import date, timedelta
from smtplib import SMTPRecipientsRefused from smtplib import SMTPRecipientsRefused
from typing import Any from typing import Any
from urllib.parse import urlparse
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django.conf import settings from django.conf import settings
@@ -34,7 +35,7 @@ from django.contrib.auth.mixins import (
) )
from django.contrib.syndication.views import Feed from django.contrib.syndication.views import Feed
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from django.db.models import Max from django.db.models import Exists, Max, OuterRef, Value
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
@@ -45,7 +46,7 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView, TemplateView, View from django.views.generic import DetailView, ListView, TemplateView, View
from django.views.generic.edit import CreateView, DeleteView, UpdateView from django.views.generic.edit import CreateView, DeleteView, UpdateView
from club.models import Club, Mailing from club.models import Club, Mailing, Membership
from com.forms import NewsDateForm, NewsForm, PosterForm from com.forms import NewsDateForm, NewsForm, PosterForm
from com.ics_calendar import IcsCalendar from com.ics_calendar import IcsCalendar
from com.models import News, NewsDate, Poster, Screen, Sith, Weekmail, WeekmailArticle from com.models import News, NewsDate, Poster, Screen, Sith, Weekmail, WeekmailArticle
@@ -561,16 +562,26 @@ class MailingModerateView(View):
raise PermissionDenied raise PermissionDenied
class PosterListBaseView(PermissionOrClubBoardRequiredMixin, ListView): class PosterListBaseView(ListView):
"""List communication posters.""" """List communication posters."""
model = Poster model = Poster
template_name = "com/poster_list.jinja" template_name = "com/poster_list.jinja"
permission_required = "com.view_poster" permission_required = "com.view_poster"
ordering = ["-date_begin"]
def get_context_data(self, **kwargs): def get_queryset(self):
return super().get_context_data(**kwargs) | {"club": self.club} qs = Poster.objects.prefetch_related("screens")
if self.request.user.has_perm("com.edit_poster"):
qs = qs.annotate(is_editable=Value(value=True))
else:
qs = qs.annotate(
is_editable=Exists(
Membership.objects.ongoing()
.board()
.filter(user=self.request.user, club=OuterRef("club_id"))
)
)
return qs.order_by("-date_begin")
class PosterCreateBaseView(PermissionOrClubBoardRequiredMixin, CreateView): class PosterCreateBaseView(PermissionOrClubBoardRequiredMixin, CreateView):
@@ -633,21 +644,17 @@ class PosterDeleteBaseView(
permission_required = "com.delete_poster" permission_required = "com.delete_poster"
class PosterListView(ComTabsMixin, PosterListBaseView): class PosterListView(PermissionRequiredMixin, ComTabsMixin, PosterListBaseView):
"""List communication posters.""" """List communication posters."""
current_tab = "posters" current_tab = "posters"
extra_context = {
def get_queryset(self): "create_url": reverse_lazy("com:poster_create"),
qs = super().get_queryset() "get_edit_url": lambda poster: reverse(
if self.request.user.has_perm("com.view_poster"): "com:poster_edit", kwargs={"poster_id": poster.id}
return qs ),
return qs.filter(club=self.club.id) }
permission_required = "com.view_poster"
def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs)
kwargs["app"] = "com"
return kwargs
class PosterCreateView(ComTabsMixin, PosterCreateBaseView): class PosterCreateView(ComTabsMixin, PosterCreateBaseView):
@@ -672,17 +679,6 @@ class PosterDeleteView(PosterDeleteBaseView):
success_url = reverse_lazy("com:poster_list") success_url = reverse_lazy("com:poster_list")
class PosterModerateListView(PermissionRequiredMixin, ComTabsMixin, ListView):
"""Moderate list communication poster."""
current_tab = "posters"
model = Poster
template_name = "com/poster_moderate.jinja"
queryset = Poster.objects.filter(is_moderated=False).all()
permission_required = "com.moderate_poster"
extra_context = {"app": "com"}
class PosterModerateView(PermissionRequiredMixin, ComTabsMixin, View): class PosterModerateView(PermissionRequiredMixin, ComTabsMixin, View):
"""Moderate communication poster.""" """Moderate communication poster."""
@@ -690,12 +686,21 @@ class PosterModerateView(PermissionRequiredMixin, ComTabsMixin, View):
permission_required = "com.moderate_poster" permission_required = "com.moderate_poster"
extra_context = {"app": "com"} extra_context = {"app": "com"}
def get(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
obj = get_object_or_404(Poster, pk=kwargs["object_id"]) obj = get_object_or_404(Poster, pk=kwargs["object_id"])
obj.is_moderated = True obj.is_moderated = True
obj.moderator = request.user obj.moderator = request.user
obj.save() obj.save()
return redirect("com:poster_moderate_list") # The moderation request may be originated from a club context (/club/poster)
# or a global context (/com/poster),
# so the redirection URL will be the URL of the page that called this view,
# as long as the latter belongs to the sith.
referer = self.request.META.get("HTTP_REFERER")
if referer:
parsed = urlparse(referer)
if parsed.netloc == settings.SITH_URL:
return redirect(parsed.path)
return redirect(reverse("com:poster_list"))
class ScreenListView(PermissionRequiredMixin, ComTabsMixin, ListView): class ScreenListView(PermissionRequiredMixin, ComTabsMixin, ListView):

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