mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-04 14:16:21 +01:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a9a2a670c | ||
|
|
5b25637de5 | ||
|
|
cf2e368978 | ||
|
|
0d67673542 | ||
|
|
d7e58fb557 | ||
|
|
f8cffb64a1 | ||
|
|
dc97fb1cc5 | ||
|
|
392a0c4dcb | ||
|
|
970a111386 | ||
|
|
cafa7cbf17 | ||
|
|
0f0c0441a9 | ||
|
|
a0db8e8e2c | ||
|
|
641356f31d | ||
|
|
191b238a04 | ||
|
|
f6936c5f33 | ||
|
|
e8f8cb8aa3 | ||
|
|
96170a668f | ||
|
|
4a3e807066 | ||
|
|
ab369d9aac | ||
|
|
742864fe6c | ||
|
|
c3df1c4d1f | ||
|
|
63d7fb80e6 | ||
|
|
a7f468efd1 | ||
|
|
9f4ab9540b | ||
|
|
1e133b7c5d | ||
|
|
4aa7530bbc | ||
|
|
2e0ddf2e7a | ||
|
|
e24bc2a05d | ||
|
|
a8c0db3fd7 | ||
|
|
7b77a6cd40 | ||
|
|
b8b8e470f2 | ||
|
|
ad92ea243d | ||
|
|
489a8456f7 | ||
|
|
122e389c38 | ||
|
|
8318add6d5 | ||
|
|
6c3650d9f2 | ||
|
|
37005b1c68 | ||
|
|
0897383e41 | ||
|
|
15db817382 | ||
|
|
eaa1cde01a | ||
|
|
7c1d1074f9 | ||
|
|
0f0f9b6062 | ||
|
|
839232dc98 | ||
|
|
6f2807cba2 | ||
|
|
39a40a8c43 |
@@ -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
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -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()
|
||||
|
||||
69
allianceauth/notifications/tests/test_handlers.py
Normal file
69
allianceauth/notifications/tests/test_handlers.py
Normal 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")
|
||||
@@ -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')
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
61
allianceauth/static/js/eve-time.js
Normal file
61
allianceauth/static/js/eve-time.js
Normal 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();
|
||||
});
|
||||
5
allianceauth/templates/admin/base_site.html
Normal file
5
allianceauth/templates/admin/base_site.html
Normal file
@@ -0,0 +1,5 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
|
||||
{% block extrahead %}
|
||||
{% include "allianceauth/icons.html" %}
|
||||
{% endblock %}
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
29
allianceauth/templates/allianceauth/top-menu-admin.html
Normal file
29
allianceauth/templates/allianceauth/top-menu-admin.html
Normal 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 %}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
```
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user