Merge branch 'master' into 'cronbuilder'

# Conflicts:
#   allianceauth/project_template/project_name/settings/base.py
This commit is contained in:
Ariel Rin 2025-01-10 11:48:03 +00:00
commit fbafcac5b1
25 changed files with 278 additions and 133 deletions

View File

@ -1,15 +1,16 @@
from django.contrib import admin from django.contrib import admin
from .models import AnalyticsIdentifier, AnalyticsTokens from .models import AnalyticsIdentifier, AnalyticsTokens
from solo.admin import SingletonModelAdmin
@admin.register(AnalyticsIdentifier) @admin.register(AnalyticsIdentifier)
class AnalyticsIdentifierAdmin(admin.ModelAdmin): class AnalyticsIdentifierAdmin(SingletonModelAdmin):
search_fields = ['identifier', ] search_fields = ['identifier', ]
list_display = ('identifier',) list_display = ['identifier', ]
@admin.register(AnalyticsTokens) @admin.register(AnalyticsTokens)
class AnalyticsTokensAdmin(admin.ModelAdmin): class AnalyticsTokensAdmin(admin.ModelAdmin):
search_fields = ['name', ] search_fields = ['name', ]
list_display = ('name', 'type',) list_display = ['name', 'type', ]

View File

@ -0,0 +1,17 @@
# Generated by Django 4.2.16 on 2024-12-11 02:17
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('analytics', '0009_remove_analyticstokens_ignore_paths_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='analyticsidentifier',
options={'verbose_name': 'Analytics Identifier'},
),
]

View File

@ -1,23 +1,19 @@
from typing import Literal
from django.db import models from django.db import models
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from solo.models import SingletonModel
from uuid import uuid4 from uuid import uuid4
class AnalyticsIdentifier(models.Model): class AnalyticsIdentifier(SingletonModel):
identifier = models.UUIDField( identifier = models.UUIDField(default=uuid4, editable=False)
default=uuid4,
editable=False)
def save(self, *args, **kwargs): def __str__(self) -> Literal['Analytics Identifier']:
if not self.pk and AnalyticsIdentifier.objects.exists(): return "Analytics Identifier"
# Force a single object
raise ValidationError('There is can be only one \ class Meta:
AnalyticsIdentifier instance') verbose_name = "Analytics Identifier"
self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1
return super().save(*args, **kwargs)
class AnalyticsTokens(models.Model): class AnalyticsTokens(models.Model):

View File

@ -5,6 +5,7 @@ from django.apps import apps
from celery import shared_task from celery import shared_task
from .models import AnalyticsTokens, AnalyticsIdentifier from .models import AnalyticsTokens, AnalyticsIdentifier
from .utils import ( from .utils import (
existence_baremetal_or_docker,
install_stat_addons, install_stat_addons,
install_stat_tokens, install_stat_tokens,
install_stat_users) install_stat_users)
@ -67,8 +68,8 @@ def analytics_event(namespace: str,
value=value).apply_async(priority=9) value=value).apply_async(priority=9)
@shared_task() @shared_task
def analytics_daily_stats(): def analytics_daily_stats() -> None:
"""Celery Task: Do not call directly """Celery Task: Do not call directly
Gathers a series of daily statistics Gathers a series of daily statistics
@ -77,6 +78,7 @@ def analytics_daily_stats():
users = install_stat_users() users = install_stat_users()
tokens = install_stat_tokens() tokens = install_stat_tokens()
addons = install_stat_addons() addons = install_stat_addons()
existence_type = existence_baremetal_or_docker()
logger.debug("Running Daily Analytics Upload") logger.debug("Running Daily Analytics Upload")
analytics_event(namespace='allianceauth.analytics', analytics_event(namespace='allianceauth.analytics',
@ -84,6 +86,11 @@ def analytics_daily_stats():
label='existence', label='existence',
value=1, value=1,
event_type='Stats') event_type='Stats')
analytics_event(namespace='allianceauth.analytics',
task='send_install_stats',
label=existence_type,
value=1,
event_type='Stats')
analytics_event(namespace='allianceauth.analytics', analytics_event(namespace='allianceauth.analytics',
task='send_install_stats', task='send_install_stats',
label='users', label='users',
@ -99,7 +106,6 @@ def analytics_daily_stats():
label='addons', label='addons',
value=addons, value=addons,
event_type='Stats') event_type='Stats')
for appconfig in apps.get_app_configs(): for appconfig in apps.get_app_configs():
if appconfig.label in [ if appconfig.label in [
"django_celery_beat", "django_celery_beat",
@ -135,7 +141,7 @@ def analytics_daily_stats():
event_type='Stats') event_type='Stats')
@shared_task() @shared_task
def send_ga_tracking_celery_event( def send_ga_tracking_celery_event(
measurement_id: str, measurement_id: str,
secret: str, secret: str,
@ -165,7 +171,7 @@ def send_ga_tracking_celery_event(
} }
payload = { payload = {
'client_id': AnalyticsIdentifier.objects.get(id=1).identifier.hex, 'client_id': AnalyticsIdentifier.get_solo().identifier.hex,
"user_properties": { "user_properties": {
"allianceauth_version": { "allianceauth_version": {
"value": __version__ "value": __version__

View File

@ -1,9 +1,8 @@
from allianceauth.analytics.models import AnalyticsIdentifier from allianceauth.analytics.models import AnalyticsIdentifier
from django.core.exceptions import ValidationError
from django.test.testcases import TestCase from django.test.testcases import TestCase
from uuid import UUID, uuid4 from uuid import uuid4
# Identifiers # Identifiers
@ -14,14 +13,4 @@ uuid_2 = "7aa6bd70701f44729af5e3095ff4b55c"
class TestAnalyticsIdentifier(TestCase): class TestAnalyticsIdentifier(TestCase):
def test_identifier_random(self): def test_identifier_random(self):
self.assertNotEqual(AnalyticsIdentifier.objects.get(), uuid4) self.assertNotEqual(AnalyticsIdentifier.get_solo(), uuid4)
def test_identifier_singular(self):
AnalyticsIdentifier.objects.all().delete()
AnalyticsIdentifier.objects.create(identifier=uuid_1)
# Yeah i have multiple asserts here, they all do the same thing
with self.assertRaises(ValidationError):
AnalyticsIdentifier.objects.create(identifier=uuid_2)
self.assertEqual(AnalyticsIdentifier.objects.count(), 1)
self.assertEqual(AnalyticsIdentifier.objects.get(
pk=1).identifier, UUID(uuid_1))

View File

@ -1,3 +1,4 @@
import os
from django.apps import apps from django.apps import apps
from allianceauth.authentication.models import User from allianceauth.authentication.models import User
from esi.models import Token from esi.models import Token
@ -34,3 +35,16 @@ def install_stat_addons() -> int:
The Number of Installed Apps""" The Number of Installed Apps"""
addons = len(list(apps.get_app_configs())) addons = len(list(apps.get_app_configs()))
return addons return addons
def existence_baremetal_or_docker() -> str:
"""Checks the Installation Type of an install
Returns
-------
str
existence_baremetal or existence_docker"""
docker_tag = os.getenv('AA_DOCKER_TAG')
if docker_tag:
return "existence_docker"
return "existence_baremetal"

View File

@ -44,8 +44,10 @@ INSTALLED_APPS = [
'allianceauth.theme.materia', 'allianceauth.theme.materia',
"allianceauth.custom_css", "allianceauth.custom_css",
'allianceauth.crontab', 'allianceauth.crontab',
'sri',
] ]
SRI_ALGORITHM = "sha512"
SECRET_KEY = "wow I'm a really bad default secret key" SECRET_KEY = "wow I'm a really bad default secret key"
# Celery configuration # Celery configuration

View File

@ -1,3 +1,3 @@
{% load static %} {% load sri %}
<link href="{% static 'allianceauth/css/auth-base.css' %}" rel="stylesheet"> {% sri_static 'allianceauth/css/auth-base.css' %}

View File

@ -1,3 +1,3 @@
{% load static %} {% load sri %}
<link href="{% static 'allianceauth/framework/css/auth-framework.css' %}" rel="stylesheet"> {% sri_static 'allianceauth/framework/css/auth-framework.css' %}

View File

@ -1,4 +1,6 @@
{% load static %} {% load static %}
{% load sri %}
<!-- Bootstrap CSS --> <!-- Bootstrap CSS -->
{% if NIGHT_MODE %} {% if NIGHT_MODE %}
{% if debug %} {% if debug %}
@ -6,7 +8,7 @@
<link rel="stylesheet/less" href="{% static 'allianceauth/css/themes/darkly/darkly.less' %}"> <link rel="stylesheet/less" href="{% static 'allianceauth/css/themes/darkly/darkly.less' %}">
<script src='https://cdnjs.cloudflare.com/ajax/libs/less.js/4.1.3/less.min.js' integrity='sha512-6gUGqd/zBCrEKbJqPI7iINc61jlOfH5A+SluY15IkNO1o4qP1DEYjQBewTB4l0U4ihXZdupg8Mb77VxqE+37dg==' crossorigin='anonymous' referrerpolicy="no-referrer"></script> <script src='https://cdnjs.cloudflare.com/ajax/libs/less.js/4.1.3/less.min.js' integrity='sha512-6gUGqd/zBCrEKbJqPI7iINc61jlOfH5A+SluY15IkNO1o4qP1DEYjQBewTB4l0U4ihXZdupg8Mb77VxqE+37dg==' crossorigin='anonymous' referrerpolicy="no-referrer"></script>
{% else %} {% else %}
<link rel="stylesheet" href="{% static 'allianceauth/css/themes/darkly/darkly.min.css' %}"> {% sri_static 'allianceauth/css/themes/darkly/darkly.min.css' %}
{% endif %} {% endif %}
{% else %} {% else %}
{% if debug %} {% if debug %}
@ -14,7 +16,7 @@
<link rel="stylesheet/less" href="{% static 'allianceauth/css/themes/flatly/flatly.less' %}"> <link rel="stylesheet/less" href="{% static 'allianceauth/css/themes/flatly/flatly.less' %}">
<script src='https://cdnjs.cloudflare.com/ajax/libs/less.js/4.1.3/less.min.js' integrity='sha512-6gUGqd/zBCrEKbJqPI7iINc61jlOfH5A+SluY15IkNO1o4qP1DEYjQBewTB4l0U4ihXZdupg8Mb77VxqE+37dg==' crossorigin='anonymous' referrerpolicy="no-referrer"></script> <script src='https://cdnjs.cloudflare.com/ajax/libs/less.js/4.1.3/less.min.js' integrity='sha512-6gUGqd/zBCrEKbJqPI7iINc61jlOfH5A+SluY15IkNO1o4qP1DEYjQBewTB4l0U4ihXZdupg8Mb77VxqE+37dg==' crossorigin='anonymous' referrerpolicy="no-referrer"></script>
{% else %} {% else %}
<link rel="stylesheet" href="{% static 'allianceauth/css/themes/flatly/flatly.min.css' %}"> {% sri_static 'allianceauth/css/themes/flatly/flatly.min.css' %}
{% endif %} {% endif %}
{% endif %} {% endif %}
<!-- End Bootstrap CSS --> <!-- End Bootstrap CSS -->

View File

@ -1,3 +1,3 @@
{% load static %} {% load sri %}
<link href="{% static 'allianceauth/css/checkbox.css' %}" rel="stylesheet"> {% sri_static 'allianceauth/css/checkbox.css' %}

View File

@ -1,3 +1,3 @@
{% load static %} {% load sri %}
<script src="{% static 'allianceauth/js/eve-time.js' %}"></script> {% sri_static 'allianceauth/js/eve-time.js' %}

View File

@ -1,3 +1,3 @@
{% load static %} {% load sri %}
<script src="{% static 'allianceauth/js/filterDropDown/filterDropDown.min.js' %}"></script> {% sri_static 'allianceauth/js/filterDropDown/filterDropDown.min.js' %}

View File

@ -1,5 +1,6 @@
{% load static %}
<!-- Start jQuery UI CSS from Alliance Auth --> <!-- Start jQuery UI CSS from Alliance Auth -->
<!-- CDNs all contain theme.css, which is not supposed to be in the base CSS, Which is why this is uniquely bundled in not using a CDN --> <!-- CDNs all contain theme.css, which is not supposed to be in the base CSS, which is why this is uniquely bundled in not using a CDN -->
<link rel="stylesheet" href="{% static 'allianceauth/js/jquery-ui/1.13.2/css/jquery-ui.min.css' %}" integrity="VEqAhOZvZrx/WaxlpMoLvZDSLeLNYhkL5LU2R4/ihPJb/+qkGoMrA15SqEGtI+PCLgKwCDiby7tgdvdiAZkJGg==" crossorigin="anonymous" referrerpolicy="no-referrer"> {% load sri %}
{% sri_static 'allianceauth/js/jquery-ui/1.13.2/css/jquery-ui.min.css' %}
<!-- End jQuery UI CSS from aa-gdpr --> <!-- End jQuery UI CSS from aa-gdpr -->

View File

@ -1,7 +1,19 @@
{% load i18n %}
<!-- Start Moment.js from cdnjs --> <!-- Start Moment.js from cdnjs -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js" integrity="sha512-+H4iLjY3JsKiF2V6N366in5IQHj2uEsGV7Pp/GRcm0fn76aPAk5V8xB6n8fQhhSonTqTXs/klFz4D0GIn6Br9g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js" integrity="sha512-+H4iLjY3JsKiF2V6N366in5IQHj2uEsGV7Pp/GRcm0fn76aPAk5V8xB6n8fQhhSonTqTXs/klFz4D0GIn6Br9g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
{% if locale and LANGUAGE_CODE != 'en' %} {% if locale and LANGUAGE_CODE != 'en' %}
<!-- Moment.JS Not EN-en --> <!-- Moment.JS Not EN-en -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/locale/{{ LANGUAGE_CODE }}.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script> {% get_current_language as LANGUAGE_CODE %}
{% get_language_info for LANGUAGE_CODE as lang %}
{% if lang.code == 'zh-hans' %}
<!-- Moment.JS Localisation ({{ lang.code }}) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/locale/zh-cn.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
{% else %}
<!-- Moment.JS Localisation ({{ lang.code }}) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/locale/{{ lang.code }}.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
{% endif %}
{% endif %} {% endif %}
<!-- End Moment JS from cdnjs --> <!-- End Moment JS from cdnjs -->

View File

@ -1,3 +1,3 @@
{% load static %} {% load sri %}
<script src="{% static 'allianceauth/js/refresh-notification-icon.js' %}"></script> {% sri_static 'allianceauth/js/refresh-notification-icon.js' %}

View File

@ -1,3 +1,3 @@
{% load static %} {% load sri %}
<script src="{% static 'allianceauth/js/refresh_notifications.js' %}"></script> {% sri_static 'allianceauth/js/refresh_notifications.js' %}

View File

@ -1,3 +1,3 @@
{% load static %} {% load sri %}
<script src="{% static 'allianceauth/js/timerboard.js' %}"></script> {% sri_static 'allianceauth/js/timerboard.js' %}

View File

@ -1,3 +1,3 @@
{% load static %} {% load sri %}
<script src="{% static 'allianceauth/js/timers.js' %}"></script> {% sri_static 'allianceauth/js/timers.js' %}

View File

@ -0,0 +1,45 @@
# Generated by Django 4.2.17 on 2025-01-06 17:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("timerboard", "0006_alter_timer_objective_alter_timer_structure_and_more"),
]
operations = [
migrations.AlterField(
model_name="timer",
name="structure",
field=models.CharField(
choices=[
("POCO", "POCO"),
("Orbital Skyhook", "Orbital Skyhook"),
("I-HUB", "Sovereignty Hub"),
("TCU", "TCU"),
("POS[S]", "POS [S]"),
("POS[M]", "POS [M]"),
("POS[L]", "POS [L]"),
("Astrahus", "Astrahus"),
("Fortizar", "Fortizar"),
("Keepstar", "Keepstar"),
("Raitaru", "Raitaru"),
("Azbel", "Azbel"),
("Sotiyo", "Sotiyo"),
("Athanor", "Athanor"),
("Tatara", "Tatara"),
("Pharolux Cyno Beacon", "Cyno Beacon"),
("Tenebrex Cyno Jammer", "Cyno Jammer"),
("Ansiblex Jump Gate", "Ansiblex Jump Gate"),
("Mercenary Den", "Mercenary Den"),
("Moon Mining Cycle", "Moon Mining Cycle"),
("Metenox Moon Drill", "Metenox Moon Drill"),
("Other", "Other"),
],
default="Other",
max_length=254,
),
),
]

View File

@ -1,6 +1,6 @@
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.utils.translation import gettext as _ from django.utils.translation import gettext_lazy as _
from allianceauth.eveonline.models import EveCharacter from allianceauth.eveonline.models import EveCharacter
from allianceauth.eveonline.models import EveCorporationInfo from allianceauth.eveonline.models import EveCorporationInfo
@ -23,7 +23,7 @@ class Timer(models.Model):
POCO = "POCO", _("POCO") POCO = "POCO", _("POCO")
ORBITALSKYHOOK = "Orbital Skyhook", _("Orbital Skyhook") ORBITALSKYHOOK = "Orbital Skyhook", _("Orbital Skyhook")
IHUB = "I-HUB", _("I-HUB") IHUB = "I-HUB", _("Sovereignty Hub")
TCU = "TCU", _("TCU") # Pending Remval TCU = "TCU", _("TCU") # Pending Remval
POSS = "POS[S]", _("POS [S]") POSS = "POS[S]", _("POS [S]")
POSM = "POS[M]", _("POS [M]") POSM = "POS[M]", _("POS [M]")
@ -36,9 +36,10 @@ class Timer(models.Model):
SOTIYO = "Sotiyo", _("Sotiyo") SOTIYO = "Sotiyo", _("Sotiyo")
ATHANOR = "Athanor", _("Athanor") ATHANOR = "Athanor", _("Athanor")
TATARA = "Tatara", _("Tatara") TATARA = "Tatara", _("Tatara")
PHAROLUX = "Pharolux Cyno Beacon", _("Pharolux Cyno Beacon") PHAROLUX = "Pharolux Cyno Beacon", _("Cyno Beacon")
TENEBREX = "Tenebrex Cyno Jammer", _("Tenebrex Cyno Jammer") TENEBREX = "Tenebrex Cyno Jammer", _("Cyno Jammer")
ANSIBLEX = "Ansiblex Jump Gate", _("Ansiblex Jump Gate") ANSIBLEX = "Ansiblex Jump Gate", _("Ansiblex Jump Gate")
MERCDEN = "Mercenary Den", _("Mercenary Den")
MOONPOP = "Moon Mining Cycle", _("Moon Mining Cycle") MOONPOP = "Moon Mining Cycle", _("Moon Mining Cycle")
METENOX = "Metenox Moon Drill", _("Metenox Moon Drill") METENOX = "Metenox Moon Drill", _("Metenox Moon Drill")
OTHER = "Other", _("Other") OTHER = "Other", _("Other")

View File

@ -19,7 +19,6 @@
{% for timer in timers %} {% for timer in timers %}
<tr class="{% if timer.important == True %}bg-danger bg-opacity-25{% else %}bg-info bg-opacity-25{% endif %}"> <tr class="{% if timer.important == True %}bg-danger bg-opacity-25{% else %}bg-info bg-opacity-25{% endif %}">
<td style="width: 150px;" class="text-center"> <td style="width: 150px;" class="text-center">
{{ timer.details }} {{ timer.details }}
@ -30,13 +29,21 @@
</td> </td>
<td class="text-center"> <td class="text-center">
{% comment %} Objective: Hostile (BG: Danger) {% endcomment %}
{% if timer.objective == "Hostile" %} {% if timer.objective == "Hostile" %}
<div class="badge bg-danger">{% translate "Hostile" %}</div> <div class="badge bg-danger">
{% comment %} Objective: Friendly (BG: Primare) {% endcomment %}
{% elif timer.objective == "Friendly" %} {% elif timer.objective == "Friendly" %}
<div class="badge bg-primary">{% translate "Friendly" %}</div> <div class="badge bg-primary">
{% comment %} Objective: Neutral (BG: Secondary) {% endcomment %}
{% elif timer.objective == "Neutral" %} {% elif timer.objective == "Neutral" %}
<div class="badge bg-secondary">{% translate "Neutral" %}</div> <div class="badge bg-secondary">
{% endif %} {% endif %}
{{ timer.get_objective_display }}
</div>
</td> </td>
<td class="text-center"> <td class="text-center">
@ -44,49 +51,9 @@
</td> </td>
<td class="text-center"> <td class="text-center">
{% if timer.structure == "POCO" %} <div class="badge bg-{{ timer.bg_modifier }}">
<div class="badge bg-info">{% translate "POCO" %}</div> {{ timer.get_structure_display }}
{% elif timer.structure == "Orbital Skyhook" %} </div>
<div class="badge bg-warning">{% translate "Orbital Skyhook" %}</div>
{% elif timer.structure == "I-HUB" %}
<div class="badge bg-warning">{% translate "I-HUB" %}</div>
{% elif timer.structure == "TCU" %} {% comment %} Pending Removal {% endcomment %}
<div class="badge bg-secondary">{% translate "TCU" %}</div>
{% elif timer.structure == "POS[S]" %}
<div class="badge bg-info">{% translate "POS [S]" %}</div>
{% elif timer.structure == "POS[M]" %}
<div class="badge bg-info">{% translate "POS [M]" %}</div>
{% elif timer.structure == "POS[L]" %}
<div class="badge bg-info">{% translate "POS [L]" %}</div>
{% elif timer.structure == "Citadel[M]" or timer.structure == "Astrahus" %}
<div class="badge bg-danger">{% translate "Astrahus" %}</div>
{% elif timer.structure == "Citadel[L]" or timer.structure == "Fortizar" %}
<div class="badge bg-danger">{% translate "Fortizar" %}</div>
{% elif timer.structure == "Citadel[XL]" or timer.structure == "Keepstar" %}
<div class="badge bg-danger">{% translate "Keepstar" %}</div>
{% elif timer.structure == "Engineering Complex[M]" or timer.structure == "Raitaru" %}
<div class="badge bg-warning">{% translate "Raitaru" %}</div>
{% elif timer.structure == "Engineering Complex[L]" or timer.structure == "Azbel" %}
<div class="badge bg-warning">{% translate "Azbel" %}</div>
{% elif timer.structure == "Engineering Complex[XL]" or timer.structure == "Sotiyo" %}
<div class="badge bg-danger">{% translate "Sotiyo" %}</div>
{% elif timer.structure == "Refinery[M]" or timer.structure == "Athanor" %}
<div class="badge bg-warning">{% translate "Athanor" %}</div>
{% elif timer.structure == "Refinery[L]" or timer.structure == "Tatara" %}
<div class="badge bg-warning">{% translate "Tatara" %}</div>
{% elif timer.structure == "Cyno Beacon" or timer.structure == "Pharolux Cyno Beacon" %}
<div class="badge bg-warning">{% translate "Cyno Beacon" %}</div>
{% elif timer.structure == "Cyno Jammer" or timer.structure == "Tenebrex Cyno Jammer" %}
<div class="badge bg-warning">{% translate "Cyno Jammer" %}</div>
{% elif timer.structure == "Jump Gate" or timer.structure == "Ansiblex Jump Gate" %}
<div class="badge bg-warning">{% translate "Ansiblex Jump Gate" %}</div>
{% elif timer.structure == "Moon Mining Cycle" %}
<div class="badge bg-success">{% translate "Moon Mining Cycle" %}</div>
{% elif timer.structure == "Metenox Moon Drill" %}
<div class="badge bg-warning">{% translate "Metenox Moon Drill" %}</div>
{% elif timer.structure == "Other" %}
<div class="badge bg-secondary">{% translate "Other" %}</div>
{% endif %}
</td> </td>
<td class="text-center" nowrap>{{ timer.eve_time | date:"Y-m-d H:i" }}</td> <td class="text-center" nowrap>{{ timer.eve_time | date:"Y-m-d H:i" }}</td>

View File

@ -1,12 +1,12 @@
import datetime
import logging import logging
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import ( from django.contrib.auth.mixins import (
LoginRequiredMixin, PermissionRequiredMixin, LoginRequiredMixin,
PermissionRequiredMixin,
) )
from django.db.models import Q from django.db.models import Q
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, render
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils import timezone from django.utils import timezone
@ -20,8 +20,8 @@ from allianceauth.timerboard.models import Timer
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
TIMER_VIEW_PERMISSION = 'auth.timer_view' TIMER_VIEW_PERMISSION = "auth.timer_view"
TIMER_MANAGE_PERMISSION = 'auth.timer_management' TIMER_MANAGE_PERMISSION = "auth.timer_management"
class BaseTimerView(LoginRequiredMixin, PermissionRequiredMixin, View): class BaseTimerView(LoginRequiredMixin, PermissionRequiredMixin, View):
@ -29,22 +29,112 @@ class BaseTimerView(LoginRequiredMixin, PermissionRequiredMixin, View):
class TimerView(BaseTimerView): class TimerView(BaseTimerView):
template_name = 'timerboard/view.html' template_name = "timerboard/view.html"
permission_required = TIMER_VIEW_PERMISSION permission_required = TIMER_VIEW_PERMISSION
def get(self, request): def get(self, request):
"""
Renders the timer view
:param request:
:type request:
:return:
:rtype:
"""
def get_bg_modifier(structure):
"""
Returns the bootstrap bg modifier for the given structure
:param structure:
:type structure:
:return:
:rtype:
"""
if structure in bg_info:
return "info"
elif structure in bg_warning:
return "warning"
elif structure in bg_danger:
return "danger"
elif structure in bg_secondary:
return "secondary"
return "primary"
logger.debug(f"timer_view called by user {request.user}") logger.debug(f"timer_view called by user {request.user}")
char = request.user.profile.main_character char = request.user.profile.main_character
if char: if char:
corp = char.corporation corp = char.corporation
else: else:
corp = None corp = None
base_query = Timer.objects.select_related('eve_character')
base_query = Timer.objects.select_related("eve_character")
timers = []
corp_timers = []
future_timers = []
past_timers = []
bg_info = [
Timer.Structure.POCO.value, # POCO
Timer.Structure.POSS.value, # POS[S]
Timer.Structure.POSM.value, # POS[M]
Timer.Structure.POSL.value, # POS[L]
]
bg_warning = [
Timer.Structure.ANSIBLEX.value, # Ansiblex Jump Gate
Timer.Structure.ATHANOR.value, # Athanor
Timer.Structure.AZBEL.value, # Azbel
Timer.Structure.MERCDEN.value, # Mercenary Den
Timer.Structure.METENOX.value, # Metenox Moon Drill
Timer.Structure.ORBITALSKYHOOK.value, # Orbital Skyhook
Timer.Structure.PHAROLUX.value, # Pharolux Cyno Beacon
Timer.Structure.RAITARU.value, # Raitaru
"Station", # Legacy structure, remove in future update
Timer.Structure.TATARA.value, # Tatara
Timer.Structure.TENEBREX.value, # Tenebrex Cyno Jammer
]
bg_danger = [
Timer.Structure.ASTRAHUS.value, # Astrahus
Timer.Structure.FORTIZAR.value, # Fortizar
Timer.Structure.IHUB.value, # I-HUB
Timer.Structure.KEEPSTAR.value, # Keepstar
Timer.Structure.SOTIYO.value, # Sotiyo
Timer.Structure.TCU.value, # TCU (Legacy structure, remove in future update)
]
bg_secondary = [
Timer.Structure.MOONPOP.value, # Moon Mining Cycle
Timer.Structure.OTHER.value, # Other
]
# Timers
for timer in base_query.filter(corp_timer=False):
timer.bg_modifier = get_bg_modifier(timer.structure)
timers.append(timer)
# Corp Timers
for timer in base_query.filter(corp_timer=True, eve_corp=corp):
timer.bg_modifier = get_bg_modifier(timer.structure)
corp_timers.append(timer)
# Future Timers
for timer in base_query.filter(corp_timer=False, eve_time__gte=timezone.now()):
timer.bg_modifier = get_bg_modifier(timer.structure)
future_timers.append(timer)
# Past Timers
for timer in base_query.filter(corp_timer=False, eve_time__lt=timezone.now()):
timer.bg_modifier = get_bg_modifier(timer.structure)
past_timers.append(timer)
render_items = { render_items = {
'timers': base_query.filter(corp_timer=False), "timers": timers,
'corp_timers': base_query.filter(corp_timer=True, eve_corp=corp), "corp_timers": corp_timers,
'future_timers': base_query.filter(corp_timer=False, eve_time__gte=timezone.now()), "future_timers": future_timers,
'past_timers': base_query.filter(corp_timer=False, eve_time__lt=timezone.now()), "past_timers": past_timers,
} }
return render(request, self.template_name, context=render_items) return render(request, self.template_name, context=render_items)
@ -52,7 +142,7 @@ class TimerView(BaseTimerView):
class TimerManagementView(BaseTimerView): class TimerManagementView(BaseTimerView):
permission_required = TIMER_MANAGE_PERMISSION permission_required = TIMER_MANAGE_PERMISSION
index_redirect = 'timerboard:view' index_redirect = "timerboard:view"
success_url = reverse_lazy(index_redirect) success_url = reverse_lazy(index_redirect)
model = Timer model = Timer
@ -66,12 +156,12 @@ class AddUpdateMixin:
Inject the request user into the kwargs passed to the form Inject the request user into the kwargs passed to the form
""" """
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs.update({'user': self.request.user}) kwargs.update({"user": self.request.user})
return kwargs return kwargs
class AddTimerView(TimerManagementView, AddUpdateMixin, CreateView): class AddTimerView(TimerManagementView, AddUpdateMixin, CreateView):
template_name_suffix = '_create_form' template_name_suffix = "_create_form"
form_class = TimerForm form_class = TimerForm
def form_valid(self, form): def form_valid(self, form):
@ -82,17 +172,18 @@ class AddTimerView(TimerManagementView, AddUpdateMixin, CreateView):
) )
messages.success( messages.success(
self.request, self.request,
_('Added new timer in %(system)s at %(time)s.') % {"system": timer.system, "time": timer.eve_time} _("Added new timer in %(system)s at %(time)s.")
% {"system": timer.system, "time": timer.eve_time},
) )
return result return result
class EditTimerView(TimerManagementView, AddUpdateMixin, UpdateView): class EditTimerView(TimerManagementView, AddUpdateMixin, UpdateView):
template_name_suffix = '_update_form' template_name_suffix = "_update_form"
form_class = TimerForm form_class = TimerForm
def form_valid(self, form): def form_valid(self, form):
messages.success(self.request, _('Saved changes to the timer.')) messages.success(self.request, _("Saved changes to the timer."))
return super().form_valid(form) return super().form_valid(form)
@ -107,21 +198,20 @@ def dashboard_timers(request):
except (EveCorporationInfo.DoesNotExist, AttributeError): except (EveCorporationInfo.DoesNotExist, AttributeError):
return "" return ""
timers = Timer.objects.select_related( timers = Timer.objects.select_related("eve_character").filter(
'eve_character'
).filter(
(Q(corp_timer=True) & Q(eve_corp=corp)) | Q(corp_timer=False), (Q(corp_timer=True) & Q(eve_corp=corp)) | Q(corp_timer=False),
eve_time__gte=timezone.now() eve_time__gte=timezone.now(),
)[:5] )[:5]
if timers.count(): if timers.count():
context = { context = {
'timers': timers, "timers": timers,
} }
return render_to_string( return render_to_string(
template_name='timerboard/dashboard.timers.html', template_name="timerboard/dashboard.timers.html",
context=context, request=request context=context,
request=request,
) )
else: else:
return "" return ""

View File

@ -27,6 +27,7 @@ Analytics comes preloaded with our Google Analytics token, and the three types o
Our Daily Stats contain the following: Our Daily Stats contain the following:
- A phone-in task to identify a server's existence - A phone-in task to identify a server's existence
- A phone-in task to identify if a server is Bare-Metal or Dockerized
- A task to send the Number of User models - A task to send the Number of User models
- A task to send the Number of Token Models - A task to send the Number of Token Models
- A task to send the Number of Installed Apps - A task to send the Number of Installed Apps

View File

@ -52,6 +52,7 @@ dependencies = [
"django-registration<3.4,>=3.3", "django-registration<3.4,>=3.3",
"django-solo", "django-solo",
"django-sortedm2m", "django-sortedm2m",
"django-sri",
"dnspython", "dnspython",
"mysqlclient>=2.1", "mysqlclient>=2.1",
"openfire-restapi", "openfire-restapi",