Compare commits

...

45 Commits

Author SHA1 Message Date
Ariel Rin
4a9a2a670c Version Bump 2.9.0b1 2021-09-14 05:08:15 +00:00
Ariel Rin
5b25637de5 Debian Bullseye now Stable, libmariadbclient-dev name deprecated, now removed 2021-09-14 05:02:15 +00:00
Ariel Rin
cf2e368978 Merge branch 'docs-updates' into 'v2.9.x'
Docs Updates

See merge request allianceauth/allianceauth!1334
2021-09-14 04:21:23 +00:00
Peter Pfeufer
0d67673542 Docs Updates 2021-09-14 04:21:22 +00:00
Ariel Rin
d7e58fb557 Merge branch 'django-admin-favicons' into 'v2.9.x'
Add favicon support to django admin backend

See merge request allianceauth/allianceauth!1335
2021-09-14 04:19:33 +00:00
Peter Pfeufer
f8cffb64a1 add favicon support to django admin backend 2021-08-25 22:37:37 +02:00
Joel Falknau
dc97fb1cc5 Forgot pre-commit for master > 2.9.x 2021-08-20 14:50:44 +10:00
Joel Falknau
392a0c4dcb Merge remote-tracking branch 'upstream/master' into v2.9.x 2021-08-20 14:41:38 +10:00
Ariel Rin
970a111386 Merge branch 'analytics' into 'v2.9.x'
Analytics Enable migration typo

See merge request allianceauth/allianceauth!1331
2021-08-20 04:04:33 +00:00
Ariel Rin
cafa7cbf17 Merge branch 'top-menu-redesign' into 'v2.9.x'
Top menu redesign

See merge request allianceauth/allianceauth!1329
2021-08-20 04:04:23 +00:00
Ariel Rin
0f0c0441a9 Merge branch 'fix_logging_notifications' into 'master'
Fix logging notifications

See merge request allianceauth/allianceauth!1332
2021-08-20 04:03:48 +00:00
Erik Kalkoken
a0db8e8e2c Fix logging notifications 2021-08-20 04:03:48 +00:00
Peter Pfeufer
641356f31d css properly formatted 2021-08-18 07:40:47 +02:00
Peter Pfeufer
191b238a04 using "public/lang_select.html" 2021-08-18 07:39:36 +02:00
Joel Falknau
f6936c5f33 Typo in token enable flag 2021-08-18 11:43:05 +10:00
Ariel Rin
e8f8cb8aa3 Merge branch 'fix-for-the-fix' into 'v2.9.x'
(Hotfix) Missing import added

See merge request allianceauth/allianceauth!1330
2021-08-18 01:39:15 +00:00
Peter Pfeufer
96170a668f missing import added 2021-08-17 23:47:15 +02:00
Peter Pfeufer
4a3e807066 menu item text for mobile view 2021-08-14 14:02:25 +02:00
Peter Pfeufer
ab369d9aac [FIX] text color 2021-08-14 13:58:01 +02:00
Peter Pfeufer
742864fe6c adding eve time to top menu 2021-08-11 16:19:13 +02:00
Peter Pfeufer
c3df1c4d1f adding user menu 2021-08-11 16:18:59 +02:00
Ariel Rin
63d7fb80e6 Merge branch 'srp_evetools_killboard' into 'v2.9.x'
Add EveTools kill board as accepted source for srp request kill mails

See merge request allianceauth/allianceauth!1326
2021-08-11 13:06:19 +00:00
Ariel Rin
a7f468efd1 Merge branch 'fix_group_creation' into 'master'
Fix Group Creation

Closes #1161

See merge request allianceauth/allianceauth!1327
2021-08-11 06:07:24 +00:00
Ariel Rin
9f4ab9540b Merge branch 'improve_notifications' into 'master'
Improve notifications

See merge request allianceauth/allianceauth!1324
2021-08-11 06:06:24 +00:00
Erik Kalkoken
1e133b7c5d Improve notifications 2021-08-11 06:06:23 +00:00
Ariel Rin
4aa7530bbc Merge branch 'fix_hasgroupleader' into 'master'
Fix `Has Leader` column for groups that have group leader groups.

See merge request allianceauth/allianceauth!1328
2021-08-11 06:06:13 +00:00
colcrunch
2e0ddf2e7a has_leader should return true when a group has a group_leader_group 2021-07-13 18:00:21 -04:00
colcrunch
e24bc2a05d Revert "Update tests."
This reverts commit 7c1d1074f9.
2021-07-13 11:35:19 -04:00
colcrunch
a8c0db3fd7 Revert "Update autogroups."
This reverts commit eaa1cde01a.
2021-07-13 11:35:06 -04:00
colcrunch
7b77a6cd40 Revert "Add authutil for creating groups with authgroups."
This reverts commit 15db817382.
2021-07-13 11:34:55 -04:00
colcrunch
b8b8e470f2 Revert "Update tests to use the create_group util."
This reverts commit 0897383e41.
2021-07-13 11:34:46 -04:00
colcrunch
ad92ea243d Revert "Fix missed test."
This reverts commit 37005b1c68.
2021-07-13 11:34:35 -04:00
colcrunch
489a8456f7 Revert "More test fixes."
This reverts commit 6c3650d9f2.
2021-07-13 11:34:19 -04:00
colcrunch
122e389c38 Add signals back. 2021-07-13 11:33:51 -04:00
colcrunch
8318add6d5 Update test_admin.py 2021-07-13 10:18:39 -04:00
colcrunch
6c3650d9f2 More test fixes. 2021-07-13 09:28:31 -04:00
colcrunch
37005b1c68 Fix missed test. 2021-07-13 09:18:53 -04:00
colcrunch
0897383e41 Update tests to use the create_group util. 2021-07-13 09:13:47 -04:00
colcrunch
15db817382 Add authutil for creating groups with authgroups. 2021-07-13 09:13:17 -04:00
colcrunch
eaa1cde01a Update autogroups. 2021-07-13 08:41:36 -04:00
colcrunch
7c1d1074f9 Update tests. 2021-07-13 08:41:25 -04:00
colcrunch
0f0f9b6062 Fix group creation ignoring AuthGroup settings. (Fixes #1161) 2021-07-13 08:09:40 -04:00
Peter Pfeufer
839232dc98 add evetools killboard as accepted source for srp requests 2021-07-04 15:04:25 +02:00
Ariel Rin
6f2807cba2 Version Bump v2.8.6 2021-07-02 16:47:06 +00:00
Ariel Rin
39a40a8c43 Limit Django-Celery-Beat to 2.2.0 for Celery 4.x 2021-07-02 16:42:07 +00:00
29 changed files with 598 additions and 148 deletions

View File

@@ -33,13 +33,13 @@ sast:
dependency_scanning:
stage: gitlab
before_script:
- apt-get update && apt-get install redis-server libmariadbclient-dev -y
- apt-get update && apt-get install redis-server libmariadb-dev -y
- redis-server --daemonize yes
- python -V
- pip install wheel tox
test-3.7-core:
image: python:3.7-buster
image: python:3.7-bullseye
script:
- tox -e py37-core
artifacts:
@@ -48,7 +48,7 @@ test-3.7-core:
cobertura: coverage.xml
test-3.8-core:
image: python:3.8-buster
image: python:3.8-bullseye
script:
- tox -e py38-core
artifacts:
@@ -57,7 +57,7 @@ test-3.8-core:
cobertura: coverage.xml
test-3.9-core:
image: python:3.9-buster
image: python:3.9-bullseye
script:
- tox -e py39-core
artifacts:
@@ -66,7 +66,7 @@ test-3.9-core:
cobertura: coverage.xml
test-3.7-all:
image: python:3.7-buster
image: python:3.7-bullseye
script:
- tox -e py37-all
artifacts:
@@ -75,7 +75,7 @@ test-3.7-all:
cobertura: coverage.xml
test-3.8-all:
image: python:3.8-buster
image: python:3.8-bullseye
script:
- tox -e py38-all
artifacts:
@@ -84,7 +84,7 @@ test-3.8-all:
cobertura: coverage.xml
test-3.9-all:
image: python:3.9-buster
image: python:3.9-bullseye
script:
- tox -e py39-all
artifacts:
@@ -94,7 +94,7 @@ test-3.9-all:
deploy_production:
stage: deploy
image: python:3.9-buster
image: python:3.9-bullseye
before_script:
- pip install twine wheel

View File

@@ -1,7 +1,7 @@
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
__version__ = '2.9.0a4'
__version__ = '2.9.0b1'
__title__ = 'Alliance Auth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
NAME = '%s v%s' % (__title__, __version__)

View File

@@ -11,7 +11,7 @@ def add_aa_team_token(apps, schema_editor):
token.type = 'GA-U'
token.token = 'UA-186249766-2'
token.send_page_views = True
token.send_celery_views = True
token.send_celery_tasks = True
token.send_stats = True
token.name = 'AA Team Public Google Analytics (Universal)'
token.save()

View File

@@ -40,9 +40,6 @@ class AuthGroupInlineAdmin(admin.StackedInline):
kwargs["queryset"] = Group.objects.order_by(Lower('name'))
return super().formfield_for_manytomany(db_field, request, **kwargs)
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
@@ -139,7 +136,7 @@ class GroupAdmin(admin.ModelAdmin):
_member_count.admin_order_field = 'member_count'
def has_leader(self, obj):
return obj.authgroup.group_leaders.exists()
return obj.authgroup.group_leaders.exists() or obj.authgroup.group_leader_groups.exists()
has_leader.boolean = True
@@ -174,6 +171,13 @@ class GroupAdmin(admin.ModelAdmin):
kwargs["queryset"] = Permission.objects.select_related("content_type").all()
return super().formfield_for_manytomany(db_field, request, **kwargs)
def save_formset(self, request, form, formset, change):
for inline_form in formset:
ag_instance = inline_form.save(commit=False)
ag_instance.group = form.instance
ag_instance.save()
formset.save()
class Group(BaseGroup):
class Meta:

View File

@@ -55,7 +55,6 @@ class RequestLog(models.Model):
return user.profile.main_character
class AuthGroup(models.Model):
"""
Extends Django Group model with a one-to-one field
@@ -106,7 +105,8 @@ class AuthGroup(models.Model):
help_text="States listed here will have the ability to join this group provided "
"they have the proper permissions.")
description = models.TextField(max_length=512, blank=True, help_text="Short description <i>(max. 512 characters)</i> of the group shown to users.")
description = models.TextField(max_length=512, blank=True, help_text="Short description <i>(max. 512 characters)"
"</i> of the group shown to users.")
def __str__(self):
return self.group.name

View File

@@ -48,6 +48,7 @@ class TestGroupAdmin(TestCase):
cls.group_2 = Group.objects.create(name='Group 2')
cls.group_2.authgroup.description = 'Internal Group'
cls.group_2.authgroup.internal = True
cls.group_2.authgroup.group_leader_groups.add(cls.group_1)
cls.group_2.authgroup.save()
# group 3 - has leader
@@ -237,10 +238,14 @@ class TestGroupAdmin(TestCase):
result = self.modeladmin._member_count(obj)
self.assertEqual(result, expected)
def test_has_leader(self):
def test_has_leader_user(self):
result = self.modeladmin.has_leader(self.group_1)
self.assertTrue(result)
def test_has_leader_group(self):
result = self.modeladmin.has_leader(self.group_2)
self.assertTrue(result)
def test_properties_1(self):
expected = ['Default']
result = self.modeladmin._properties(self.group_1)

View File

@@ -1,4 +1,35 @@
from django.contrib import admin
from .models import Notification
admin.site.register(Notification)
@admin.register(Notification)
class NotificationAdmin(admin.ModelAdmin):
list_display = ("timestamp", "_main", "_state", "title", "level", "viewed")
list_select_related = ("user", "user__profile__main_character", "user__profile__state")
list_filter = (
"level",
"timestamp",
"user__profile__state",
('user__profile__main_character', admin.RelatedOnlyFieldListFilter),
)
ordering = ("-timestamp", )
search_fields = ["user__username", "user__profile__main_character__character_name"]
def _main(self, obj):
try:
return obj.user.profile.main_character
except AttributeError:
return obj.user
_main.admin_order_field = "user__profile__main_character__character_name"
def _state(self, obj):
return obj.user.profile.state
_state.admin_order_field = "user__profile__state__name"
def has_change_permission(self, request, obj=None):
return False
def has_add_permission(self, request) -> bool:
return False

View File

@@ -12,21 +12,20 @@ class NotificationHandler(logging.Handler):
try:
perm = Permission.objects.get(codename="logging_notifications")
message = record.getMessage()
if record.exc_text:
message += "\n\n"
message = message + record.exc_text
users = User.objects.filter(
Q(groups__permissions=perm) | Q(user_permissions=perm) | Q(is_superuser=True)).distinct()
for user in users:
notify(
user,
"%s [%s:%s]" % (record.levelname, record.funcName, record.lineno),
level=str([item[0] for item in Notification.LEVEL_CHOICES if item[1] == record.levelname][0]),
message=message
)
except Permission.DoesNotExist:
pass
return
message = record.getMessage()
if record.exc_text:
message += "\n\n"
message = message + record.exc_text
users = User.objects.filter(
Q(groups__permissions=perm) | Q(user_permissions=perm) | Q(is_superuser=True)).distinct()
for user in users:
notify(
user,
"%s [%s:%s]" % (record.levelname, record.funcName, record.lineno),
level=Notification.Level.from_old_name(record.levelname),
message=message
)

View File

@@ -12,7 +12,7 @@ class NotificationQuerySet(models.QuerySet):
"""Custom QuerySet for Notification model"""
def update(self, *args, **kwargs):
# overriden update to ensure cache is invaidated on very call
"""Override update to ensure cache is invalidated on very call."""
super().update(*args, **kwargs)
user_pks = set(self.select_related("user").values_list('user__pk', flat=True))
for user_pk in user_pks:
@@ -43,6 +43,8 @@ class NotificationManager(models.Manager):
if not message:
message = title
if level not in self.model.Level:
level = self.model.Level.INFO
obj = self.create(user=user, title=title, message=message, level=level)
logger.info("Created notification %s", obj)
return obj

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.1.12 on 2021-07-01 21:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('notifications', '0004_performance_tuning'),
]
operations = [
migrations.AlterField(
model_name='notification',
name='level',
field=models.CharField(choices=[('danger', 'danger'), ('warning', 'warning'), ('info', 'info'), ('success', 'success')], default='info', max_length=10),
),
]

View File

@@ -2,6 +2,7 @@ import logging
from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _
from .managers import NotificationManager
@@ -14,16 +15,42 @@ class Notification(models.Model):
NOTIFICATIONS_MAX_PER_USER_DEFAULT = 50
NOTIFICATIONS_REFRESH_TIME_DEFAULT = 30
LEVEL_CHOICES = (
('danger', 'CRITICAL'),
('danger', 'ERROR'),
('warning', 'WARN'),
('info', 'INFO'),
('success', 'DEBUG'),
)
class Level(models.TextChoices):
"""A notification level."""
DANGER = 'danger', _('danger') #:
WARNING = 'warning', _('warning') #:
INFO = 'info', _('info') #:
SUCCESS = 'success', _('success') #:
@classmethod
def from_old_name(cls, name: str) -> object:
"""Map old name to enum.
Raises ValueError for invalid names.
"""
name_map = {
"CRITICAL": cls.DANGER,
"ERROR": cls.DANGER,
"WARN": cls.WARNING,
"INFO": cls.INFO,
"DEBUG": cls.SUCCESS,
}
try:
return name_map[name]
except KeyError:
raise ValueError(f"Unknown name: {name}") from None
# LEVEL_CHOICES = (
# ('danger', 'CRITICAL'),
# ('danger', 'ERROR'),
# ('warning', 'WARN'),
# ('info', 'INFO'),
# ('success', 'DEBUG'),
# )
user = models.ForeignKey(User, on_delete=models.CASCADE)
level = models.CharField(choices=LEVEL_CHOICES, max_length=10)
level = models.CharField(choices=Level.choices, max_length=10, default=Level.INFO)
title = models.CharField(max_length=254)
message = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
@@ -45,22 +72,15 @@ class Notification(models.Model):
Notification.objects.invalidate_user_notification_cache(self.user.pk)
def mark_viewed(self) -> None:
"""mark notification as viewed"""
"""Mark notification as viewed."""
logger.info("Marking notification as viewed: %s" % self)
self.viewed = True
self.save()
def set_level(self, level_name: str) -> None:
"""set notification level according to level name, e.g. 'CRITICAL'
"""Set notification level according to old level name, e.g. 'CRITICAL'.
raised exception on invalid level names
Raises ValueError on invalid level names.
"""
try:
new_level = [
item[0] for item in self.LEVEL_CHOICES if item[1] == level_name
][0]
except IndexError:
raise ValueError('Invalid level name: %s' % level_name)
self.level = new_level
self.level = self.Level.from_old_name(level_name)
self.save()

View File

@@ -0,0 +1,69 @@
from logging import LogRecord, DEBUG
from django.contrib.auth.models import Permission, Group, User
from django.test import TestCase
from allianceauth.tests.auth_utils import AuthUtils
from ..handlers import NotificationHandler
from ..models import Notification
MODULE_PATH = 'allianceauth.notifications.handlers'
class TestHandler(TestCase):
def test_do_nothing_if_permission_does_not_exist(self):
# given
Permission.objects.get(codename="logging_notifications").delete()
handler = NotificationHandler()
record = LogRecord(
name="name",
level=DEBUG,
pathname="pathname",
lineno=42,
msg="msg",
args=[],
exc_info=None,
func="func"
)
# when
handler.emit(record)
# then
self.assertEqual(Notification.objects.count(), 0)
def test_should_emit_message_to_users_with_permission_only(self):
# given
AuthUtils.create_user('Lex Luthor')
user_permission = AuthUtils.create_user('Bruce Wayne')
user_permission = AuthUtils.add_permission_to_user_by_name(
"auth.logging_notifications", user_permission
)
group = Group.objects.create(name="Dummy Group")
perm = Permission.objects.get(codename="logging_notifications")
group.permissions.add(perm)
user_group = AuthUtils.create_user('Peter Parker')
user_group.groups.add(group)
user_superuser = User.objects.create_superuser("Clark Kent")
handler = NotificationHandler()
record = LogRecord(
name="name",
level=DEBUG,
pathname="pathname",
lineno=42,
msg="msg",
args=[],
exc_info=None,
func="func"
)
# when
handler.emit(record)
# then
self.assertEqual(Notification.objects.count(), 3)
users = set(Notification.objects.values_list("user__pk", flat=True))
self.assertSetEqual(
users, {user_permission.pk, user_group.pk, user_superuser.pk}
)
notif = Notification.objects.first()
self.assertEqual(notif.user, user_permission)
self.assertEqual(notif.title, "DEBUG [func:42]")
self.assertEqual(notif.level, "success")
self.assertEqual(notif.message, "msg")

View File

@@ -65,6 +65,35 @@ class TestUserNotify(TestCase):
self.assertEqual(obj.title, title)
self.assertEqual(obj.message, title)
def test_should_use_default_level_when_not_specified(self):
# given
title = 'dummy_title'
message = 'dummy message'
# when
Notification.objects.notify_user(self.user, title, message)
# then
self.assertEqual(Notification.objects.filter(user=self.user).count(), 1)
obj = Notification.objects.first()
self.assertEqual(obj.user, self.user)
self.assertEqual(obj.title, title)
self.assertEqual(obj.message, message)
self.assertEqual(obj.level, Notification.Level.INFO)
def test_should_use_default_level_when_invalid_level_given(self):
# given
title = 'dummy_title'
message = 'dummy message'
level = "invalid"
# when
Notification.objects.notify_user(self.user, title, message, level)
# then
self.assertEqual(Notification.objects.filter(user=self.user).count(), 1)
obj = Notification.objects.first()
self.assertEqual(obj.user, self.user)
self.assertEqual(obj.title, title)
self.assertEqual(obj.message, message)
self.assertEqual(obj.level, Notification.Level.INFO)
@override_settings(NOTIFICATIONS_MAX_PER_USER=3)
def test_remove_when_too_many_notifications(self):
Notification.objects.notify_user(self.user, 'dummy')

View File

@@ -12,6 +12,7 @@ from django.contrib import messages
from celery.schedules import crontab
INSTALLED_APPS = [
'allianceauth', # needs to be on top of this list to support favicons in Django admin (see https://gitlab.com/allianceauth/allianceauth/-/issues/1301)
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
@@ -23,7 +24,6 @@ INSTALLED_APPS = [
'bootstrapform',
'sortedm2m',
'esi',
'allianceauth',
'allianceauth.authentication',
'allianceauth.services',
'allianceauth.eveonline',

View File

@@ -1,4 +1,5 @@
from django.conf.urls import include, url
from django.core.exceptions import ObjectDoesNotExist
from django.template.loader import render_to_string
from django.utils.functional import cached_property
from django.conf import settings

View File

@@ -13,7 +13,7 @@ class SrpFleetMainForm(forms.Form):
class SrpFleetUserRequestForm(forms.Form):
additional_info = forms.CharField(required=False, max_length=25, label=_("Additional Info"))
killboard_link = forms.CharField(
label=_("zKillboard Link"),
label=_("Killboard Link (zkillboard.com or kb.evetools.org)"),
max_length=255,
required=True
@@ -21,13 +21,31 @@ class SrpFleetUserRequestForm(forms.Form):
def clean_killboard_link(self):
data = self.cleaned_data['killboard_link']
if "zkillboard.com" not in data:
raise forms.ValidationError(_("Invalid Link. Please use zKillboard.com"))
if not re.match(r"http[s]?://zkillboard\.com/kill/\d+\/", data):
# Check if it's a link from one of the accepted kill boards
if not any(
re.match(regex, data)
for regex in [
r"^http[s]?:\/\/zkillboard\.com\/",
r"^http[s]?:\/\/kb\.evetools\.org\/",
]
):
raise forms.ValidationError(
_("Invalid Link. Please use zkillboard.com or kb.evetools.org")
)
# Check if it's an actual kill mail
if not any(
re.match(regex, data)
for regex in [
r"^http[s]?:\/\/zkillboard\.com\/kill\/\d+\/",
r"^http[s]?:\/\/kb\.evetools\.org\/kill\/\d+",
]
):
raise forms.ValidationError(
_("Invalid Link. Please post a direct link to a killmail.")
)
return data

View File

@@ -170,9 +170,13 @@ def srp_request_view(request, fleet_srp):
logger.debug("Request type POST contains form valid: %s" % form.is_valid())
if form.is_valid():
if SrpUserRequest.objects.filter(killboard_link=form.cleaned_data['killboard_link']).exists():
request_killboard_link = form.cleaned_data['killboard_link']
killmail_id = SRPManager.get_kill_id(killboard_link=request_killboard_link)
# check if the killmail_id is already present
if SrpUserRequest.objects.filter(killboard_link__icontains="/kill/" + killmail_id).exists():
messages.error(request,
_("This Killboard link has already been posted."))
_("This kill mail has already been posted."))
return redirect("srp:management")
character = request.user.profile.main_character
@@ -180,7 +184,7 @@ def srp_request_view(request, fleet_srp):
post_time = timezone.now()
srp_request = SrpUserRequest()
srp_request.killboard_link = form.cleaned_data['killboard_link']
srp_request.killboard_link = request_killboard_link
srp_request.additional_info = form.cleaned_data['additional_info']
srp_request.character = character
srp_request.srp_fleet_main = srp_fleet_main

View File

@@ -3,7 +3,7 @@
}
.notification-bell-color {
color: #A88F1E;
color: #a88f1e;
}
.navbar-brand {
@@ -20,12 +20,12 @@
}
#wrapper {
width: 100%;
padding: 0;
width: 100%;
}
#site-body-wrapper {
margin-right:0;
margin-right: 0;
}
/* Horizontal list group */
@@ -46,7 +46,7 @@ ul.list-group.list-group-horizontal > li.list-group-item {
@media all {
/* style nav tabs in dark mode*/
.template-dark-mode .nav-tabs > li.active > a {
background-color: rgb(70, 69, 69)!important;
background-color: rgb(70, 69, 69) !important;
color: rgb(255, 255, 255) !important;
}
@@ -58,40 +58,47 @@ ul.list-group.list-group-horizontal > li.list-group-item {
/* style group headers within a table */
.template-light-mode .tr-group {
font-weight: bold;
background-color: #e6e6e6 !important;
}
.template-dark-mode .tr-group {
font-weight: bold;
}
.template-dark-mode .tr-group {
background-color: rgb(105, 105, 105) !important;
font-weight: bold;
}
/* default style for tables */
.template-light-mode .table-aa > thead > tr > th{
.template-light-mode .table-aa > thead > tr > th {
border-bottom: 1px solid #f2f2f2;
}
.template-dark-mode .table-aa > thead > tr > th{
.template-dark-mode .table-aa > thead > tr > th {
border-bottom: 1px solid rgb(70, 69, 69);
}
.table-aa > thead > tr > th{
.table-aa > thead > tr > th {
vertical-align: middle;
}
.template-light-mode .table-aa > tbody > tr > td{
.template-light-mode .table-aa > tbody > tr > td {
border-bottom: 1px solid #f2f2f2;
}
.template-dark-mode .table-aa > tbody > tr > td{
.template-dark-mode .table-aa > tbody > tr > td {
border-bottom: 1px solid rgb(70, 69, 69);
}
.table-aa > tbody > tr > td {
vertical-align: middle;
}
.table-aa > tbody > tr:last-child {
border-bottom: none;
}
}
/* highlight active menu items
--------------------------------------------------------------------------------------------------------------------- */
------------------------------------------------------------------------------------- */
@media all {
.template-light-mode .nav-pills > li > a.active {
background-color: rgb(236, 240, 241);
@@ -102,15 +109,99 @@ ul.list-group.list-group-horizontal > li.list-group-item {
}
}
/* user menu
------------------------------------------------------------------------------------- */
@media all {
img {
height: auto;
max-width: 100%;
}
.navbar-nav > li.top-user-menu.with-main-character > .dropdown-menu {
padding-top: 0.5rem;
}
.navbar-nav > li.top-user-menu.with-main-character a {
padding: 14px;
}
.dropdown-menu > li > a {
clear: both;
color: rgb(123, 138, 139);
display: block;
font-weight: 400;
line-height: 1.42857143;
padding: 3px 20px;
white-space: nowrap;
}
.top-user-menu {
color: rgb(255, 255, 255);
}
.top-menu-bar-language-select form {
border-bottom: 1px solid transparent;
border-top: 1px solid transparent;
-webkit-box-shadow: inset 0 1px 0 rgb(255 255 255), 0 1px 0 rgb(255 255 255);
box-shadow: inset 0 1px 0 rgb(255 255 255), 0 1px 0 rgb(255 255 255);
margin-bottom: 7.5px;
margin-left: 5px;
margin-right: 5px;
margin-top: 7.5px;
padding: 10px 15px;
}
}
@media all and (max-width: 768px) {
.navbar-nav .open .dropdown-menu .dropdown-header, .navbar-nav .open .dropdown-menu > li > a {
padding: 5px 15px 5px 25px;
}
}
@media all and (min-width: 768px) {
.top-user-menu {
color: rgb(123, 138, 139);
}
.top-menu-bar-language-select form {
border: 0;
-webkit-box-shadow: none;
box-shadow: none;
margin-left: 0;
margin-right: 0;
padding-bottom: 0;
padding-top: 0;
width: auto;
}
}
/* eve time in navbar
------------------------------------------------------------------------------------- */
@media all {
.nav-item-eve-time .eve-time-wrapper {
color: rgb(255, 255, 255);
display: block;
line-height: 21px;
padding: 10px 15px;
position: relative;
}
}
@media all and (min-width: 768px) {
.nav-item-eve-time .eve-time-wrapper {
padding-bottom: 19.5px;
padding-top: 19.5px;
}
}
/* Small devices (tablets, 768px and up) */
@media (min-width: 768px) {
/* class for vertically aligning columns in a bootstrap row */
.row.vertical-flexbox-row2 {
display: -webkit-box;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
display: -ms-flexbox;
display: flex;
flex-wrap: wrap;
}
@@ -123,6 +214,6 @@ ul.list-group.list-group-horizontal > li.list-group-item {
/* Extra Small devices (Phones, <768px) */
@media (max-width: 767px) {
.button-wrapper .btn {
margin-bottom:5px;
margin-bottom: 5px;
}
}

View File

@@ -0,0 +1,61 @@
$(document).ready(function () {
'use strict';
/**
* check time
* @param i
* @returns {string}
*/
let checkTime = function (i) {
if (i < 10) {
i = '0' + i;
}
return i;
};
/**
* render a JS clock for Eve Time
* @param element
* @param utcOffset
*/
let renderClock = function (element, utcOffset) {
let today = new Date();
let h = today.getUTCHours();
let m = today.getUTCMinutes();
let s = today.getUTCSeconds();
h = h + utcOffset;
if (h > 24) {
h = h - 24;
}
if (h < 0) {
h = h + 24;
}
h = checkTime(h);
m = checkTime(m);
s = checkTime(s);
// document.getElementById('clock').innerHTML = h + ":" + m + ":" + s;
element.html(h + ':' + m + ':' + s);
setTimeout(function () {
renderClock(element, 0);
}, 500);
};
/**
* functions that need to be executed on load
*/
let init = function () {
renderClock($('.eve-time-wrapper .eve-time-clock'), 0);
};
/**
* start the show
*/
init();
});

View File

@@ -0,0 +1,5 @@
{% extends "admin/base_site.html" %}
{% block extrahead %}
{% include "allianceauth/icons.html" %}
{% endblock %}

View File

@@ -50,6 +50,7 @@
};
</script>
<script src="{% static 'js/refresh_notifications.js' %}"></script>
<script src="{% static 'js/eve-time.js' %}"></script>
{% block extra_javascript %}
{% endblock extra_javascript %}

View File

@@ -1,5 +1,8 @@
{% load i18n %}
<a href="{% url 'nightmode' %}?next={{ request.path|urlencode }}">
{% trans "Night" %}
<i class="fas {% if NIGHT_MODE %}fa-toggle-on{% else %}fa-toggle-off{% endif %}" aria-hidden="true"></i>
</a>
<li>
<a href="{% url 'nightmode' %}?next={{ request.path|urlencode }}">
<i class="fas {% if NIGHT_MODE %}fa-toggle-on{% else %}fa-toggle-off{% endif %}" aria-hidden="true"></i>
{% trans "Night Mode" %}
</a>
</li>

View File

@@ -0,0 +1,29 @@
{% load i18n %}
{% if user.is_authenticated %}
{% if user.is_staff %}
<li role="separator" class="divider"></li>
<li><a href="{% url 'admin:index' %}" target="_blank" rel="noopener noreferer">
<i class="fas fa-user-shield"></i>
{% translate "Admin" %}</a>
</li>
{% endif %}
{% endif %}
{% if user.is_superuser %}
<li role="separator" class="divider"></li>
<li>
<a href="https://allianceauth.readthedocs.io/" target="_blank" rel="noopener noreferer">
<i class="fas fa-question-circle fa-fw"></i>
{% translate "AA Documentation" %}
</a>
</li>
<li>
<a href="https://discord.gg/fjnHAmk" target="_blank" rel="noopener noreferer">
<i class="fab fa-discord fa-fw"></i>
{% translate "AA Support Discord" %}
</a>
</li>
{% endif %}

View File

@@ -0,0 +1,61 @@
{% load i18n %}
{% load evelinks %}
<li class="top-user-menu dropdown{% if request.user.profile.main_character %} with-main-character{% endif %}">
<a href="#" class="dropdown-toggle" type="button" id="top-user-menu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
{% if request.user.profile.main_character %}
{% with request.user.profile.main_character as main %}
<img class="img-rounded ra-avatar" src="{{ main.character_id|character_portrait_url:32 }}" alt="{{ main.character_name }}">
<span class="hidden-sm hidden-md hidden-lg">
{{ main.character_name }} - {% translate "User Menu" %}
</span>
{% endwith %}
{% else %}
{% translate "User Menu" %}
{% endif %}
<span class="caret"></span>
</a>
<ul class="dropdown-menu top-user-menu" aria-labelledby="top-user-menu">
<!-- user avatar -->
{% if request.user.profile.main_character %}
<li>
{% with request.user.profile.main_character as main %}
<p class="text-center">
<img class="img-rounded ra-avatar" src="{{ main.character_id|character_portrait_url:256 }}" alt="{{ main.character_name }}">
</p>
<p class="text-center">
<span style="display: block;">{{ main.character_name }}</span>
<span style="display: block;">{{ main.corporation_name }}</span>
{% if main.alliance_name %}
<span style="display: block;">{{ main.alliance_name }}</span>
{% endif %}
</p>
{% endwith %}
</li>
<li role="separator" class="divider"></li>
{% endif %}
<li class="top-menu-bar-language-select">
{% include 'public/lang_select.html' %}
</li>
<li role="separator" class="divider"></li>
<!-- night mode toggle -->
{% include 'allianceauth/night-toggle.html' %}
<!-- admin related menu items -->
{% include 'allianceauth/top-menu-admin.html' %}
<!-- logout / login -->
<li role="separator" class="divider"></li>
{% if user.is_authenticated %}
<li><a href="{% url 'logout' %}">{% translate "Logout" %}</a></li>
{% else %}
<li><a href="{% url 'authentication:login' %}">{% translate "Login" %}</a></li>
{% endif %}
</ul>
</li>

View File

@@ -10,51 +10,24 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand">
<img src="{% static 'icons/favicon-32x32.png' %}" style="display: inline-block;" height="32" width="32"/>
{{ SITE_NAME }}
</a>
</div>
<div class="collapse navbar-collapse auth-menus-collapse">
<ul class="nav navbar-nav navbar-right navbar-pills">
<li>
{% include 'allianceauth/night-toggle.html' %}
</li>
<li
class="{% navactive request 'notifications:' %}" id="menu_item_notifications"
>
<li class="nav-item-eve-time">
<div class="eve-time-wrapper">Eve Time: <span class="eve-time-clock"></span></div>
</li>
<li class="{% navactive request 'notifications:' %}" id="menu_item_notifications">
{% include 'allianceauth/notifications_menu_item.html' %}
</li>
{% if user.is_authenticated %}
{% if user.is_staff %}
<li><a href="{% url 'admin:index' %}">{% trans "Admin" %}</a></li>
{% endif %}
<li><a href="{% url 'logout' %}">{% trans "Logout" %}</a></li>
{% else %}
<li><a href="{% url 'authentication:login' %}">{% trans "Login" %}</a></li>
{% endif %}
{% if user.is_superuser %}
<li>
<a class="navbar-brand" style="margin-left: -4px;" href="https://allianceauth.readthedocs.io/" target="_blank">
<i class="fas fa-question-circle fa-fw"></i>
</a>
</li>
{% endif %}
</li>
{% include 'allianceauth/top-menu-user-dropdown.html' %}
</ul>
<form id="f-lang-select" class="navbar-form navbar-right" action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<div class="form-group">
<select onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %}
selected="selected"{% endif %}>
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
</div>
</form>
</div>
</div>
</nav>

View File

@@ -16,8 +16,8 @@ In addition all tools described in this guide are open source or free software.
The development environment consists of the following components:
- Visual Studio Code with Remote WSL and Python extension
- WSL with Ubunutu 18.04. LTS
- Python 3.6 environment on WSL
- WSL with Ubuntu 18.04. LTS
- Python 3.7 environment on WSL
- MySQL server on WSL
- Redis on WSL
- Alliance Auth on WSL
@@ -71,7 +71,7 @@ sudo apt-get install gettext
### Install Python
For AA we want to develop with Python 3.6, because that provides the maximum compatibility with today's AA installations. This also happens to be the default Python 3 version for Ubuntu 18.04. at the point of this writing.
For AA we want to develop with Python 3.7, because that provides the maximum compatibility with today's AA installations.
```eval_rst
.. hint::
@@ -80,7 +80,7 @@ For AA we want to develop with Python 3.6, because that provides the maximum com
```eval_rst
.. note::
Should your Ubuntu come with a newer version of Python we recommend to still setup your dev environment with the oldest Python 3 version supported by AA, e.g Python 3.6
Should your Ubuntu come with a newer version of Python we recommend to still setup your dev environment with the oldest Python 3 version supported by AA, e.g Python 3.7
You an check out this `page <https://askubuntu.com/questions/682869/how-do-i-install-a-different-python-version-using-apt-get/1195153>`_ on how to install additional Python versions on Ubuntu.
```

View File

@@ -8,18 +8,15 @@ Location: ``allianceauth.notifications``
.. automodule:: allianceauth.notifications.__init__
:members: notify
:undoc-members:
models
===========
.. autoclass:: allianceauth.notifications.models.Notification
:members: LEVEL_CHOICES, mark_viewed, set_level
:undoc-members:
:members: Level, mark_viewed, set_level
managers
===========
.. autoclass:: allianceauth.notifications.managers.NotificationManager
:members: notify_user, user_unread_count
:undoc-members:

View File

@@ -18,28 +18,57 @@ This document describes how to install **Alliance Auth** from scratch.
Alliance Auth can be installed on any Unix like operating system. Dependencies are provided below for two of the most popular Linux platforms: Ubuntu and CentOS. To install on your favorite flavour of Linux, identify and install equivalent packages to the ones listed here.
```eval_rst
.. hint::
CentOS: A few packages are included in a non-default repository. Add it and update the package lists. ::
yum -y install https://centos7.iuscommunity.org/ius-release.rpm
yum update
```
### Python
Alliance Auth requires Python 3.6 or higher. Ensure it is installed on your server before proceeding.
Alliance Auth requires Python 3.7 or higher. Ensure it is installed on your server before proceeding.
Ubuntu:
Ubuntu 1604 1804:
```bash
apt-get install python3 python3-dev python3-venv python3-setuptools python3-pip
```eval_rst
.. note::
Ubuntu 2004 ships with Python 3.8, No updates required.
```
CentOS:
```bash
add-apt-repository ppa:deadsnakes/ppa
```
```bash
yum install python36u python36u-devel python36u-setuptools python36u-pip
apt-get update
```
```bash
apt-get install python3.7 python3.7-dev python3.7-venv
```
CentOS 7/8:
```bash
cd ~
```
```bash
sudo yum install gcc openssl-devel bzip2-devel libffi-devel wget
```
```bash
wget https://www.python.org/ftp/python/3.7.11/Python-3.7.11.tgz
```
```bash
tar xvf Python-3.7.11.tgz
```
```bash
cd Python-3.7.11/
```
```bash
./configure --enable-optimizations --enable-shared
```
```bash
make altinstall
```
### Database
@@ -155,7 +184,7 @@ python3 -m venv /home/allianceserver/venv/auth/
```eval_rst
.. warning::
The python3 command may not be available on all installations. Try a specific version such as ``python3.6`` if this is the case.
The python3 command may not be available on all installations. Try a specific version such as ``python3.7`` if this is the case.
```
```eval_rst

View File

@@ -53,15 +53,15 @@ sudo yum install gcc openssl-devel bzip2-devel libffi-devel wget
```
```bash
wget https://www.python.org/ftp/python/3.7.10/Python-3.7.10.tgz
wget https://www.python.org/ftp/python/3.7.11/Python-3.7.11.tgz
```
```bash
tar xvf Python-3.7.10.tgz
tar xvf Python-3.7.11.tgz
```
```bash
cd Python-3.7.10/
cd Python-3.7.11/
```
```bash
@@ -182,7 +182,7 @@ mv /home/allianceserver/venv/auth /home/allianceserver/venv/auth_old
## Create your new venv
Now let's create our new venv with Python 3.6 and activate it:
Now let's create our new venv with Python 3.7 and activate it:
```bash
python3.7 -m venv /home/allianceserver/venv/auth