mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-04 06:06:19 +01:00
Compare commits
153 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f4dffe930 | ||
|
|
56d70e6c74 | ||
|
|
5e14ea4573 | ||
|
|
c743eca0f7 | ||
|
|
2002f24178 | ||
|
|
6412aedf53 | ||
|
|
939df08b95 | ||
|
|
d8506aa753 | ||
|
|
3f2cdac658 | ||
|
|
d57ab01ff3 | ||
|
|
91b62bbe9d | ||
|
|
557a52e3c8 | ||
|
|
f8fefd92a5 | ||
|
|
f2c43ee921 | ||
|
|
99945b0146 | ||
|
|
abb9dc4db6 | ||
|
|
eba5b80cde | ||
|
|
5b39c887a5 | ||
|
|
183363e789 | ||
|
|
d8704f4d8f | ||
|
|
165ee44a63 | ||
|
|
e8f508cecb | ||
|
|
3044f18900 | ||
|
|
79637020f3 | ||
|
|
2d34422e2d | ||
|
|
6b932b1188 | ||
|
|
f62153c746 | ||
|
|
88216c3f81 | ||
|
|
dc983c31e3 | ||
|
|
4204c44bde | ||
|
|
8da0122d17 | ||
|
|
c9fcf6e6bf | ||
|
|
36866cc59b | ||
|
|
298bdd98ed | ||
|
|
819018748d | ||
|
|
32e8e0fdd0 | ||
|
|
7625060a12 | ||
|
|
672cb13bfe | ||
|
|
7170f75b89 | ||
|
|
8f60c7a00a | ||
|
|
34ae6e402c | ||
|
|
0905e48994 | ||
|
|
02fcf7d500 | ||
|
|
8d8da50946 | ||
|
|
c1499d173f | ||
|
|
b149baa4e5 | ||
|
|
4807c69b5e | ||
|
|
ebefa0e307 | ||
|
|
468e7433f9 | ||
|
|
3ca313f907 | ||
|
|
820065fc04 | ||
|
|
3eddeefe28 | ||
|
|
82d7d7e3bf | ||
|
|
93194b4f2d | ||
|
|
fa335253d3 | ||
|
|
d1af9416b3 | ||
|
|
f4ac2ea400 | ||
|
|
31c1f8bb7d | ||
|
|
57f7178f1e | ||
|
|
17d4a4c415 | ||
|
|
18ce433fa0 | ||
|
|
1eadb1d934 | ||
|
|
59a8f8a967 | ||
|
|
2dc07b5519 | ||
|
|
3454520dfe | ||
|
|
7a195d4158 | ||
|
|
b73072dec0 | ||
|
|
1ca5e38bd9 | ||
|
|
ecb737c6a5 | ||
|
|
7063f53cdf | ||
|
|
017424b9d4 | ||
|
|
f6c26cf2ec | ||
|
|
9a422bd4ca | ||
|
|
47fec23f2e | ||
|
|
399ef1917d | ||
|
|
9db443ba54 | ||
|
|
0f2f5ea0ba | ||
|
|
1f781c5037 | ||
|
|
36dedfcbd2 | ||
|
|
13a05606fb | ||
|
|
90ad7790e1 | ||
|
|
6b8341ab5a | ||
|
|
d15f42b3fd | ||
|
|
cc60b26f5a | ||
|
|
36ff0af993 | ||
|
|
f17c94a9e1 | ||
|
|
7e3ba476f3 | ||
|
|
dd1313a2a9 | ||
|
|
763003bd7d | ||
|
|
f3217443dd | ||
|
|
a713ae1914 | ||
|
|
5815bac0df | ||
|
|
6154d2c2e7 | ||
|
|
b34661b35d | ||
|
|
a9a7e03b80 | ||
|
|
23c797ef64 | ||
|
|
da102618a0 | ||
|
|
51ee281b14 | ||
|
|
9133232c20 | ||
|
|
9cbabee126 | ||
|
|
4026523a2e | ||
|
|
7fbf96623b | ||
|
|
273bda173e | ||
|
|
7bd5838ea1 | ||
|
|
b232d9ab17 | ||
|
|
a11b870664 | ||
|
|
a27aae5d1c | ||
|
|
117ef63d90 | ||
|
|
1bde3d5672 | ||
|
|
d2355b1ec8 | ||
|
|
191d474a8e | ||
|
|
ec9a9733be | ||
|
|
cf7a8cedf1 | ||
|
|
18cbb994d5 | ||
|
|
663388a0c2 | ||
|
|
7a943591ec | ||
|
|
cd189927fe | ||
|
|
8772349309 | ||
|
|
cf20100cb5 | ||
|
|
9b9c2ddc04 | ||
|
|
34839e8344 | ||
|
|
89ef4f4cbc | ||
|
|
2cc7f46aae | ||
|
|
8d255fb720 | ||
|
|
67cf68ad87 | ||
|
|
db1971d4c2 | ||
|
|
63c1521cba | ||
|
|
ba7ef11505 | ||
|
|
d2e494b9be | ||
|
|
98bab0b180 | ||
|
|
c4efb2a11f | ||
|
|
94e4895f29 | ||
|
|
70eb1b5b50 | ||
|
|
e247a94db3 | ||
|
|
714431c932 | ||
|
|
b026277ab0 | ||
|
|
11855f0b54 | ||
|
|
635fbfe2c8 | ||
|
|
b10233daf0 | ||
|
|
1aa3187491 | ||
|
|
59f17a88f0 | ||
|
|
75db3195d4 | ||
|
|
afe3fea757 | ||
|
|
1072c00a28 | ||
|
|
b221c1ce24 | ||
|
|
1617c775ee | ||
|
|
cc88a02001 | ||
|
|
ae5d0f4a2f | ||
|
|
067e2c424e | ||
|
|
d64675a3b0 | ||
|
|
17a6b3225e | ||
|
|
b83f591dc2 | ||
|
|
74651dd30a |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -69,11 +69,7 @@ celerybeat-schedule
|
||||
#gitlab configs
|
||||
.gitlab/
|
||||
|
||||
#transifex
|
||||
.tx/
|
||||
|
||||
#other
|
||||
.flake8
|
||||
.pylintrc
|
||||
Makefile
|
||||
.isort.cfg
|
||||
|
||||
@@ -25,7 +25,7 @@ before_script:
|
||||
pre-commit-check:
|
||||
<<: *only-default
|
||||
stage: pre-commit
|
||||
image: python:3.8-bullseye
|
||||
image: python:3.10-bullseye
|
||||
variables:
|
||||
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
|
||||
cache:
|
||||
@@ -89,7 +89,7 @@ test-3.10-core:
|
||||
|
||||
test-3.11-core:
|
||||
<<: *only-default
|
||||
image: python:3.11-rc-bullseye
|
||||
image: python:3.11-bullseye
|
||||
script:
|
||||
- tox -e py311-core
|
||||
artifacts:
|
||||
@@ -98,6 +98,18 @@ test-3.11-core:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
|
||||
test-pvpy-core:
|
||||
<<: *only-default
|
||||
image: pypy:3.9-bullseye
|
||||
script:
|
||||
- tox -e pypy-all
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
allow_failure: true
|
||||
|
||||
test-3.8-all:
|
||||
@@ -138,7 +150,7 @@ test-3.10-all:
|
||||
|
||||
test-3.11-all:
|
||||
<<: *only-default
|
||||
image: python:3.11-rc-bullseye
|
||||
image: python:3.11-bullseye
|
||||
script:
|
||||
- tox -e py311-all
|
||||
artifacts:
|
||||
@@ -147,6 +159,19 @@ test-3.11-all:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
|
||||
|
||||
test-pvpy-all:
|
||||
<<: *only-default
|
||||
image: pypy:3.9-bullseye
|
||||
script:
|
||||
- tox -e pypy-all
|
||||
artifacts:
|
||||
when: always
|
||||
reports:
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
allow_failure: true
|
||||
|
||||
build-test:
|
||||
|
||||
6
.isort.cfg
Normal file
6
.isort.cfg
Normal file
@@ -0,0 +1,6 @@
|
||||
[settings]
|
||||
profile=django
|
||||
sections=FUTURE,STDLIB,THIRDPARTY,DJANGO,ESI,FIRSTPARTY,LOCALFOLDER
|
||||
known_esi=esi
|
||||
known_django=django
|
||||
skip_gitignore=true
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.3.0
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-case-conflict
|
||||
- id: check-json
|
||||
@@ -13,27 +13,49 @@ repos:
|
||||
- id: check-yaml
|
||||
- id: fix-byte-order-marker
|
||||
- id: trailing-whitespace
|
||||
exclude: (\.min\.css|\.min\.js|\.mo|\.po|swagger\.json)$
|
||||
exclude: |
|
||||
(?x)(
|
||||
\.min\.css|
|
||||
\.min\.js|
|
||||
\.po|
|
||||
\.mo|
|
||||
swagger\.json
|
||||
)
|
||||
- id: end-of-file-fixer
|
||||
exclude: (\.min\.css|\.min\.js|\.mo|\.po|swagger\.json)$
|
||||
exclude: |
|
||||
(?x)(
|
||||
\.min\.css|
|
||||
\.min\.js|
|
||||
\.po|
|
||||
\.mo|
|
||||
swagger\.json
|
||||
)
|
||||
- id: mixed-line-ending
|
||||
args: [ '--fix=lf' ]
|
||||
- id: fix-encoding-pragma
|
||||
args: [ '--remove' ]
|
||||
|
||||
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
|
||||
rev: 2.4.0
|
||||
rev: 2.7.1
|
||||
hooks:
|
||||
- id: editorconfig-checker
|
||||
exclude: ^(LICENSE|allianceauth\/static\/allianceauth\/css\/themes\/bootstrap-locals.less|allianceauth\/eveonline\/swagger.json|(.*.po)|(.*.mo))
|
||||
exclude: |
|
||||
(?x)(
|
||||
LICENSE|
|
||||
allianceauth\/static\/allianceauth\/css\/themes\/bootstrap-locals.less|
|
||||
\.po|
|
||||
\.mo|
|
||||
swagger\.json
|
||||
)
|
||||
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.34.0
|
||||
rev: v3.3.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [ --py38-plus ]
|
||||
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v1.20.1
|
||||
rev: v2.2.0
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
args: [ --include-version-classifiers ]
|
||||
|
||||
10
.tx/config
Normal file
10
.tx/config
Normal file
@@ -0,0 +1,10 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = zh-Hans: zh_Hans
|
||||
|
||||
[o:alliance-auth:p:alliance-auth:r:django-po]
|
||||
file_filter = allianceauth/locale/<lang>/LC_MESSAGES/django.po
|
||||
source_file = allianceauth/locale/en/LC_MESSAGES/django.po
|
||||
source_lang = en
|
||||
type = PO
|
||||
minimum_perc = 0
|
||||
10
.tx/config_20230406134150.bak
Executable file
10
.tx/config_20230406134150.bak
Executable file
@@ -0,0 +1,10 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = zh-Hans:zh_Hans
|
||||
|
||||
[alliance-auth.django-po]
|
||||
file_filter = allianceauth/locale/<lang>/LC_MESSAGES/django.po
|
||||
minimum_perc = 0
|
||||
source_file = allianceauth/locale/en/LC_MESSAGES/django.po
|
||||
source_lang = en
|
||||
type = PO
|
||||
7
MANIFEST.in
Normal file
7
MANIFEST.in
Normal file
@@ -0,0 +1,7 @@
|
||||
include LICENSE
|
||||
include README.md
|
||||
include MANIFEST.in
|
||||
graft allianceauth
|
||||
|
||||
global-exclude __pycache__
|
||||
global-exclude *.py[co]
|
||||
@@ -36,7 +36,7 @@ Main features:
|
||||
|
||||
- Can be easily extended with additional services and apps. Many are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
|
||||
|
||||
- English :flag_gb:, Chinese :flag_cn:, German :flag_de:, Spanish :flag_es:, Korean :flag_kr: and Russian :flag_ru: localization
|
||||
- English :flag_gb:, Chinese :flag_cn:, German :flag_de:, Spanish :flag_es:, Korean :flag_kr:, Russian :flag_ru:, Italian :flag_it:, French :flag_fr:, Japanese :flag_jp: and Ukrainian :flag_ua: Localization
|
||||
|
||||
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](http://allianceauth.rtfd.io).
|
||||
|
||||
@@ -56,13 +56,15 @@ Here is an example of the Alliance Auth web site with some plug-ins apps and ser
|
||||
|
||||
- [Aaron Kable](https://gitlab.com/aaronkable/)
|
||||
- [Ariel Rin](https://gitlab.com/soratidus999/)
|
||||
- [Basraah](https://gitlab.com/basraah/)
|
||||
- [Col Crunch](https://gitlab.com/colcrunch/)
|
||||
- [Erik Kalkoken](https://gitlab.com/ErikKalkoken/)
|
||||
- [Rounon Dax](https://gitlab.com/ppfeufer)
|
||||
- [snipereagle1](https://gitlab.com/mckernanin)
|
||||
|
||||
### Former Developers
|
||||
|
||||
- [Adarnof](https://gitlab.com/adarnof/)
|
||||
- [Basraah](https://gitlab.com/basraah/)
|
||||
|
||||
### Beta Testers / Bug Fixers
|
||||
|
||||
|
||||
@@ -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__ = '3.0.0'
|
||||
__version__ = '3.5.1'
|
||||
__title__ = 'Alliance Auth'
|
||||
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
||||
NAME = f'{__title__} v{__version__}'
|
||||
|
||||
@@ -8,13 +8,13 @@ from uuid import uuid4
|
||||
class AnalyticsIdentifier(models.Model):
|
||||
|
||||
identifier = models.UUIDField(default=uuid4,
|
||||
editable=False)
|
||||
editable=False)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.pk and AnalyticsIdentifier.objects.exists():
|
||||
# Force a single object
|
||||
raise ValidationError('There is can be only one \
|
||||
AnalyticsIdentifier instance')
|
||||
AnalyticsIdentifier instance')
|
||||
self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import logging
|
||||
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from django.contrib import messages
|
||||
|
||||
from .models import UserProfile, CharacterOwnership, OwnershipRecord
|
||||
|
||||
@@ -37,7 +38,13 @@ class StateBackend(ModelBackend):
|
||||
ownership = CharacterOwnership.objects.get(character__character_id=token.character_id)
|
||||
if ownership.owner_hash == token.character_owner_hash:
|
||||
logger.debug(f'Authenticating {ownership.user} by ownership of character {token.character_name}')
|
||||
return ownership.user
|
||||
if ownership.user.profile.main_character:
|
||||
if ownership.user.profile.main_character.character_id == token.character_id:
|
||||
return ownership.user
|
||||
else: ## this is an alt, enforce main only.
|
||||
if request:
|
||||
messages.error("Unable to authenticate with this Character, Please log in with the main character associated with this account.")
|
||||
return None
|
||||
else:
|
||||
logger.debug(f'{token.character_name} has changed ownership. Creating new user account.')
|
||||
ownership.delete()
|
||||
@@ -57,13 +64,20 @@ class StateBackend(ModelBackend):
|
||||
if records.exists():
|
||||
# we've seen this character owner before. Re-attach to their old user account
|
||||
user = records[0].user
|
||||
if user.profile.main_character:
|
||||
if ownership.user.profile.main_character.character_id != token.character_id:
|
||||
## this is an alt, enforce main only due to trust issues in SSO.
|
||||
if request:
|
||||
messages.error("Unable to authenticate with this Character, Please log in with the main character associated with this account. Then add this character from the dashboard.")
|
||||
return None
|
||||
|
||||
token.user = user
|
||||
co = CharacterOwnership.objects.create_by_token(token)
|
||||
logger.debug(f'Authenticating {user} by matching owner hash record of character {co.character}')
|
||||
if not user.profile.main_character:
|
||||
# set this as their main by default if they have none
|
||||
user.profile.main_character = co.character
|
||||
user.profile.save()
|
||||
|
||||
# set this as their main by default as they have none
|
||||
user.profile.main_character = co.character
|
||||
user.profile.save()
|
||||
return user
|
||||
logger.debug(f'Unable to authenticate character {token.character_name}. Creating new user.')
|
||||
return self.create_user(token)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 4.0.10 on 2023-05-28 15:36
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("authentication", "0020_userprofile_language_userprofile_night_mode"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="userprofile",
|
||||
name="language",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("en", "English"),
|
||||
("de", "German"),
|
||||
("es", "Spanish"),
|
||||
("zh-hans", "Chinese Simplified"),
|
||||
("ru", "Russian"),
|
||||
("ko", "Korean"),
|
||||
("fr", "French"),
|
||||
("ja", "Japanese"),
|
||||
("it", "Italian"),
|
||||
("uk", "Ukrainian"),
|
||||
],
|
||||
default="",
|
||||
max_length=10,
|
||||
verbose_name="Language",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -18,13 +18,13 @@ class State(models.Model):
|
||||
priority = models.IntegerField(unique=True, help_text="Users get assigned the state with the highest priority available to them.")
|
||||
|
||||
member_characters = models.ManyToManyField(EveCharacter, blank=True,
|
||||
help_text="Characters to which this state is available.")
|
||||
help_text="Characters to which this state is available.")
|
||||
member_corporations = models.ManyToManyField(EveCorporationInfo, blank=True,
|
||||
help_text="Corporations to whose members this state is available.")
|
||||
help_text="Corporations to whose members this state is available.")
|
||||
member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True,
|
||||
help_text="Alliances to whose members this state is available.")
|
||||
help_text="Alliances to whose members this state is available.")
|
||||
member_factions = models.ManyToManyField(EveFactionInfo, blank=True,
|
||||
help_text="Factions to whose members this state is available.")
|
||||
help_text="Factions to whose members this state is available.")
|
||||
public = models.BooleanField(default=False, help_text="Make this state available to any character.")
|
||||
|
||||
objects = StateManager()
|
||||
@@ -63,6 +63,22 @@ class UserProfile(models.Model):
|
||||
class Meta:
|
||||
default_permissions = ('change',)
|
||||
|
||||
class Language(models.TextChoices):
|
||||
"""
|
||||
Choices for UserProfile.language
|
||||
"""
|
||||
|
||||
ENGLISH = 'en', _('English')
|
||||
GERMAN = 'de', _('German')
|
||||
SPANISH = 'es', _('Spanish')
|
||||
CHINESE = 'zh-hans', _('Chinese Simplified')
|
||||
RUSSIAN = 'ru', _('Russian')
|
||||
KOREAN = 'ko', _('Korean')
|
||||
FRENCH = 'fr', _('French')
|
||||
JAPANESE = 'ja', _('Japanese')
|
||||
ITALIAN = 'it', _('Italian')
|
||||
UKRAINIAN = 'uk', _('Ukrainian')
|
||||
|
||||
user = models.OneToOneField(
|
||||
User,
|
||||
related_name='profile',
|
||||
@@ -76,20 +92,9 @@ class UserProfile(models.Model):
|
||||
State,
|
||||
on_delete=models.SET_DEFAULT,
|
||||
default=get_guest_state_pk)
|
||||
LANGUAGE_CHOICES = [
|
||||
('en', _('English')),
|
||||
('de', _('German')),
|
||||
('es', _('Spanish')),
|
||||
('zh-hans', _('Chinese Simplified')),
|
||||
('ru', _('Russian')),
|
||||
('ko', _('Korean')),
|
||||
('fr', _('French')),
|
||||
('ja', _('Japanese')),
|
||||
('it', _('Italian')),
|
||||
]
|
||||
language = models.CharField(
|
||||
_("Language"), max_length=10,
|
||||
choices=LANGUAGE_CHOICES,
|
||||
choices=Language.choices,
|
||||
blank=True,
|
||||
default='')
|
||||
night_mode = models.BooleanField(
|
||||
|
||||
@@ -6,24 +6,26 @@ CSS for allianceauth admin site
|
||||
.img-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.column-user_profile_pic {
|
||||
width: 1px;
|
||||
white-space: nowrap;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
/* tooltip */
|
||||
.tooltip {
|
||||
position: relative ;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tooltip:hover::after {
|
||||
content: attr(data-tooltip) ;
|
||||
position: absolute ;
|
||||
top: 1.1em ;
|
||||
left: 1em ;
|
||||
min-width: 200px ;
|
||||
border: 1px #808080 solid ;
|
||||
padding: 8px ;
|
||||
color: black ;
|
||||
background-color: rgb(255, 255, 204) ;
|
||||
z-index: 1 ;
|
||||
background-color: rgb(255 255 204);
|
||||
border: 1px rgb(128 128 128) solid;
|
||||
color: rgb(0 0 0);
|
||||
content: attr(data-tooltip);
|
||||
left: 1em;
|
||||
min-width: 200px;
|
||||
padding: 8px;
|
||||
position: absolute;
|
||||
top: 1.1em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
<div class="panel panel-primary" style="height:100%">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% blocktrans with state=request.user.profile.state %}
|
||||
{% blocktranslate with state=request.user.profile.state %}
|
||||
Main Character (State: {{ state }})
|
||||
{% endblocktrans %}
|
||||
{% endblocktranslate %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
@@ -103,13 +103,17 @@
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 button-wrapper">
|
||||
<a href="{% url 'authentication:add_character' %}" class="btn btn-block btn-info"
|
||||
title="Add Character">{% translate 'Add Character' %}</a>
|
||||
<div class="col-sm-6">
|
||||
<p>
|
||||
<a href="{% url 'authentication:add_character' %}" class="btn btn-block btn-info"
|
||||
title="Add Character">{% translate 'Add Character' %}</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-6 button-wrapper">
|
||||
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-block btn-info"
|
||||
title="Change Main Character">{% translate "Change Main" %}</a>
|
||||
<div class="col-sm-6">
|
||||
<p>
|
||||
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-block btn-info"
|
||||
title="Change Main Character">{% translate "Change Main" %}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -159,7 +163,7 @@
|
||||
</td>
|
||||
<td class="text-center">{{ char.character_name }}</td>
|
||||
<td class="text-center">{{ char.corporation_name }}</td>
|
||||
<td class="text-center">{{ char.alliance_name }}</td>
|
||||
<td class="text-center">{{ char.alliance_name|default:"" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% translate "Dashboard" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="page-header text-center">{% translate "Token Management" %}</h1>
|
||||
<div class="col-sm-12">
|
||||
<table class="table table-aa" id="table_tokens" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% translate "Scopes" %}</th>
|
||||
<th class="text-right">{% translate "Actions" %}</th>
|
||||
<th>{% translate "Character" %}</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for t in tokens %}
|
||||
<tr>
|
||||
<td styl="white-space:initial;">{% for s in t.scopes.all %}<span class="label label-default">{{s.name}}</span> {% endfor %}</td>
|
||||
<td nowrap class="text-right"><a href="{% url 'authentication:token_delete' t.id %}" class="btn btn-danger"><i class="fas fa-trash"></i></a> <a href="{% url 'authentication:token_refresh' t.id %}" class="btn btn-success"><i class="fas fa-sync-alt"></i></a></td>
|
||||
<td>{{t.character_name}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% translate "This page is a best attempt, but backups or database logs can still contain your tokens. Always revoke tokens on https://community.eveonline.com/support/third-party-applications/ where possible."|urlize %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
{% include 'bundles/datatables-css.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
$(document).ready(function(){
|
||||
let grp = 2;
|
||||
var table = $('#table_tokens').DataTable({
|
||||
"columnDefs": [{ orderable: false, targets: [0,1] },{ "visible": false, "targets": grp }],
|
||||
"order": [[grp, 'asc']],
|
||||
"drawCallback": function (settings) {
|
||||
var api = this.api();
|
||||
var rows = api.rows({ page: 'current' }).nodes();
|
||||
var last = null;
|
||||
api.column(grp, { page: 'current' })
|
||||
.data()
|
||||
.each(function (group, i) {
|
||||
if (last !== group) {
|
||||
$(rows).eq(i).before('<tr class="info"><td colspan="3">' + group + '</td></tr>');
|
||||
last = group;
|
||||
}
|
||||
});
|
||||
},
|
||||
"stateSave": true,
|
||||
});
|
||||
});
|
||||
{% endblock %}
|
||||
@@ -1,4 +1,5 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
@@ -7,7 +8,7 @@
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<meta property="og:title" content="{{ SITE_NAME }}">
|
||||
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'allianceauth/icons/apple-touch-icon.png' %}">
|
||||
<meta property="og:image" content="{{ SITE_URL }}{% static 'allianceauth/icons/apple-touch-icon.png' %}">
|
||||
<meta property="og:description" content="Alliance Auth - An auth system for EVE Online to help in-game organizations manage online service access.">
|
||||
|
||||
{% include 'allianceauth/icons.html' %}
|
||||
@@ -31,6 +32,7 @@
|
||||
.panel-transparent {
|
||||
background: rgba(48, 48, 48, 0.7);
|
||||
color: #ffffff;
|
||||
padding-bottom: 21px;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
{% 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 }})
|
||||
{{ language.name_local|capfirst }} ({{ language.code }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{% extends 'public/base.html' %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
{% if messages %}
|
||||
@@ -6,6 +9,7 @@
|
||||
<div class="alert alert-{{ message.level_tag}}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div class="panel panel-default panel-transparent">
|
||||
<div class="panel-body">
|
||||
<div class="col-md-12">
|
||||
@@ -13,10 +17,25 @@
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'public/lang_select.html' %}
|
||||
|
||||
<p class="text-center" style="margin-top: 2rem;">
|
||||
{% translate "For information on SSO, ESI and security read the CCP Dev Blog" %}<br>
|
||||
<a href="https://www.eveonline.com/article/introducing-esi" target="_blank" rel="noopener noreferrer">
|
||||
{% translate "Introducing ESI - A New API For Eve Online" %}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p class="text-center">
|
||||
<a href="https://community.eveonline.com/support/third-party-applications/" target="_blank" rel="noopener noreferrer">
|
||||
{% translate "Manage ESI Applications" %}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_include %}
|
||||
{% include 'bundles/bootstrap-js.html' %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -116,10 +116,17 @@ class TestAuthenticate(TestCase):
|
||||
user = StateBackend().authenticate(token=t)
|
||||
self.assertEqual(user, self.user)
|
||||
|
||||
""" Alt Login disabled
|
||||
def test_authenticate_alt_character(self):
|
||||
t = Token(character_id=self.alt_character.character_id, character_owner_hash='2')
|
||||
user = StateBackend().authenticate(token=t)
|
||||
self.assertEqual(user, self.user)
|
||||
"""
|
||||
|
||||
def test_authenticate_alt_character_fail(self):
|
||||
t = Token(character_id=self.alt_character.character_id, character_owner_hash='2')
|
||||
user = StateBackend().authenticate(token=t)
|
||||
self.assertEqual(user, None)
|
||||
|
||||
def test_authenticate_unclaimed_character(self):
|
||||
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='3')
|
||||
@@ -128,6 +135,7 @@ class TestAuthenticate(TestCase):
|
||||
self.assertEqual(user.username, 'Unclaimed_Character')
|
||||
self.assertEqual(user.profile.main_character, self.unclaimed_character)
|
||||
|
||||
""" Alt Login disabled
|
||||
def test_authenticate_character_record(self):
|
||||
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4')
|
||||
OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4')
|
||||
@@ -135,6 +143,15 @@ class TestAuthenticate(TestCase):
|
||||
self.assertEqual(user, self.old_user)
|
||||
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists())
|
||||
self.assertTrue(user.profile.main_character)
|
||||
"""
|
||||
|
||||
def test_authenticate_character_record_fails(self):
|
||||
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4')
|
||||
OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4')
|
||||
user = StateBackend().authenticate(token=t)
|
||||
self.assertEqual(user, self.old_user)
|
||||
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists())
|
||||
self.assertTrue(user.profile.main_character)
|
||||
|
||||
def test_iterate_username(self):
|
||||
t = Token(character_id=self.unclaimed_character.character_id,
|
||||
|
||||
@@ -22,5 +22,20 @@ urlpatterns = [
|
||||
views.add_character,
|
||||
name='add_character'
|
||||
),
|
||||
path(
|
||||
'account/tokens/manage/',
|
||||
views.token_management,
|
||||
name='token_management'
|
||||
),
|
||||
path(
|
||||
'account/tokens/delete/<int:token_id>',
|
||||
views.token_delete,
|
||||
name='token_delete'
|
||||
),
|
||||
path(
|
||||
'account/tokens/refresh/<int:token_id>',
|
||||
views.token_refresh,
|
||||
name='token_refresh'
|
||||
),
|
||||
path('dashboard/', views.dashboard, name='dashboard'),
|
||||
]
|
||||
|
||||
@@ -61,6 +61,44 @@ def dashboard(request):
|
||||
}
|
||||
return render(request, 'authentication/dashboard.html', context)
|
||||
|
||||
@login_required
|
||||
def token_management(request):
|
||||
tokens = request.user.token_set.all()
|
||||
|
||||
context = {
|
||||
'tokens': tokens
|
||||
}
|
||||
return render(request, 'authentication/tokens.html', context)
|
||||
|
||||
@login_required
|
||||
def token_delete(request, token_id=None):
|
||||
try:
|
||||
token = Token.objects.get(id=token_id)
|
||||
if request.user == token.user:
|
||||
token.delete()
|
||||
messages.success(request, "Token Deleted.")
|
||||
else:
|
||||
messages.error(request, "This token does not belong to you.")
|
||||
except Token.DoesNotExist:
|
||||
messages.warning(request, "Token does not exist")
|
||||
return redirect('authentication:token_management')
|
||||
|
||||
@login_required
|
||||
def token_refresh(request, token_id=None):
|
||||
try:
|
||||
token = Token.objects.get(id=token_id)
|
||||
if request.user == token.user:
|
||||
try:
|
||||
token.refresh()
|
||||
messages.success(request, "Token refreshed.")
|
||||
except Exception as e:
|
||||
messages.warning(request, f"Failed to refresh token. {e}")
|
||||
else:
|
||||
messages.error(request, "This token does not belong to you.")
|
||||
except Token.DoesNotExist:
|
||||
messages.warning(request, "Token does not exist")
|
||||
return redirect('authentication:token_management')
|
||||
|
||||
|
||||
@login_required
|
||||
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES)
|
||||
|
||||
@@ -5,5 +5,6 @@ from .views import NightModeRedirectView
|
||||
def auth_settings(request):
|
||||
return {
|
||||
'SITE_NAME': settings.SITE_NAME,
|
||||
'SITE_URL': settings.SITE_URL,
|
||||
'NIGHT_MODE': NightModeRedirectView.night_mode_state(request),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.0.7 on 2022-08-14 16:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('eveonline', '0016_character_names_are_not_unique'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='eveallianceinfo',
|
||||
name='alliance_name',
|
||||
field=models.CharField(max_length=254, db_index=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='evecorporationinfo',
|
||||
name='corporation_name',
|
||||
field=models.CharField(max_length=254, db_index=True),
|
||||
),
|
||||
]
|
||||
@@ -71,7 +71,7 @@ class EveAllianceInfo(models.Model):
|
||||
"""An alliance in Eve Online."""
|
||||
|
||||
alliance_id = models.PositiveIntegerField(unique=True)
|
||||
alliance_name = models.CharField(max_length=254, unique=True)
|
||||
alliance_name = models.CharField(max_length=254, db_index=True)
|
||||
alliance_ticker = models.CharField(max_length=254)
|
||||
executor_corp_id = models.PositiveIntegerField()
|
||||
|
||||
@@ -139,7 +139,7 @@ class EveCorporationInfo(models.Model):
|
||||
"""A corporation in Eve Online."""
|
||||
|
||||
corporation_id = models.PositiveIntegerField(unique=True)
|
||||
corporation_name = models.CharField(max_length=254, unique=True)
|
||||
corporation_name = models.CharField(max_length=254, db_index=True)
|
||||
corporation_ticker = models.CharField(max_length=254)
|
||||
member_count = models.IntegerField()
|
||||
ceo_id = models.PositiveIntegerField(blank=True, null=True, default=None)
|
||||
|
||||
@@ -8,6 +8,7 @@ from django.conf import settings
|
||||
from esi.clients import esi_client_factory
|
||||
|
||||
from allianceauth import __version__
|
||||
from allianceauth.utils.django import StartupCommand
|
||||
|
||||
|
||||
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(
|
||||
@@ -175,15 +176,16 @@ class EveProvider:
|
||||
|
||||
class EveSwaggerProvider(EveProvider):
|
||||
def __init__(self, token=None, adapter=None):
|
||||
if settings.DEBUG:
|
||||
if settings.DEBUG or StartupCommand().is_management_command:
|
||||
self._client = None
|
||||
logger.info(
|
||||
'DEBUG mode detected: ESI client will be loaded on-demand.'
|
||||
)
|
||||
logger.info('ESI client will be loaded on-demand')
|
||||
else:
|
||||
logger.info('Loading ESI client')
|
||||
try:
|
||||
self._client = esi_client_factory(
|
||||
token=token, spec_file=SWAGGER_SPEC_PATH, app_info_text=("allianceauth v" + __version__)
|
||||
token=token,
|
||||
spec_file=SWAGGER_SPEC_PATH,
|
||||
app_info_text=f"allianceauth v{__version__}"
|
||||
)
|
||||
except (HTTPError, RefResolutionError):
|
||||
logger.exception(
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<td class="text-center">{{ fat.user }}</td>
|
||||
<td class="text-center">{{ fat.character.character_name }}</td>
|
||||
{% if fat.station != "No Station" %}
|
||||
<td class="text-center">{% blocktrans %}Docked in {% endblocktrans %}{{ fat.system }}</td>
|
||||
<td class="text-center">{% blocktranslate %}Docked in {% endblocktranslate %}{{ fat.system }}</td>
|
||||
{% else %}
|
||||
<td class="text-center">{{ fat.system }}</td>
|
||||
{% endif %}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
|
||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
||||
{% if char_id %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'fatlink:user_statistics_month' char_id previous_month|date:'Y' previous_month|date:'m' %}" class="btn btn-info">{% translate "Previous month" %}</a>
|
||||
@@ -14,11 +14,11 @@
|
||||
{% endif %}
|
||||
</h1>
|
||||
<h2>
|
||||
{% blocktrans count links=n_fats trimmed %}
|
||||
{% blocktranslate count links=n_fats trimmed %}
|
||||
{{ user }} has collected one link this month.
|
||||
{% plural %}
|
||||
{{ user }} has collected {{ links }} links this month.
|
||||
{% endblocktrans %}
|
||||
{% endblocktranslate %}
|
||||
</h2>
|
||||
<table class="table table-responsive">
|
||||
<tr>
|
||||
@@ -34,11 +34,11 @@
|
||||
</table>
|
||||
{% if created_fats %}
|
||||
<h2>
|
||||
{% blocktrans count links=n_created_fats trimmed %}
|
||||
{% blocktranslate count links=n_created_fats trimmed %}
|
||||
{{ user }} has created one link this month.
|
||||
{% plural %}
|
||||
{{ user }} has created {{ links }} links this month.
|
||||
{% endblocktrans %}
|
||||
{% endblocktranslate %}
|
||||
</h2>
|
||||
{% if created_fats %}
|
||||
<table class="table">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ year }}{% endblocktrans %}
|
||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ year }}{% endblocktranslate %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'fatlink:personal_statistics_year' previous_year %}" class="btn btn-info">{% translate "Previous year" %}</a>
|
||||
{% if next_year %}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
|
||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'fatlink:statistics_corp_month' corpid previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
|
||||
{% if next_month %}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
|
||||
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'fatlink:statistics_month' previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
|
||||
{% if next_month %}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<td class="text-center">{{ fat.fatlink.fleet }}</td>
|
||||
<td class="text-center">{{ fat.character.character_name }}</td>
|
||||
{% if fat.station != "No Station" %}
|
||||
<td class="text-center">{% blocktrans %}Docked in {% endblocktrans %}{{ fat.system }}</td>
|
||||
<td class="text-center">{% blocktranslate %}Docked in {% endblocktranslate %}{{ fat.system }}</td>
|
||||
{% else %}
|
||||
<td class="text-center">{{ fat.system }}</td>
|
||||
{% endif %}
|
||||
|
||||
@@ -248,59 +248,82 @@ def fatlink_monthly_personal_statistics_view(request, year, month, char_id=None)
|
||||
|
||||
@login_required
|
||||
@token_required(
|
||||
scopes=['esi-location.read_location.v1', 'esi-location.read_ship_type.v1', 'esi-universe.read_structures.v1'])
|
||||
scopes=[
|
||||
'esi-location.read_location.v1',
|
||||
'esi-location.read_ship_type.v1',
|
||||
'esi-universe.read_structures.v1',
|
||||
'esi-location.read_online.v1',
|
||||
]
|
||||
)
|
||||
def click_fatlink_view(request, token, fat_hash=None):
|
||||
fatlink = get_object_or_404(Fatlink, hash=fat_hash)
|
||||
c = token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
|
||||
character = EveCharacter.objects.get_character_by_id(token.character_id)
|
||||
character_online = c.Location.get_characters_character_id_online(
|
||||
character_id=token.character_id
|
||||
).result()
|
||||
|
||||
if (timezone.now() - fatlink.fatdatetime) < datetime.timedelta(seconds=(fatlink.duration * 60)):
|
||||
if character_online["online"] is True:
|
||||
fatlink = get_object_or_404(Fatlink, hash=fat_hash)
|
||||
|
||||
character = EveCharacter.objects.get_character_by_id(token.character_id)
|
||||
if (timezone.now() - fatlink.fatdatetime) < datetime.timedelta(seconds=(fatlink.duration * 60)):
|
||||
if character:
|
||||
# get data
|
||||
location = c.Location.get_characters_character_id_location(character_id=token.character_id).result()
|
||||
ship = c.Location.get_characters_character_id_ship(character_id=token.character_id).result()
|
||||
location['solar_system_name'] = \
|
||||
c.Universe.get_universe_systems_system_id(system_id=location['solar_system_id']).result()['name']
|
||||
|
||||
if character:
|
||||
# get data
|
||||
c = token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
|
||||
location = c.Location.get_characters_character_id_location(character_id=token.character_id).result()
|
||||
ship = c.Location.get_characters_character_id_ship(character_id=token.character_id).result()
|
||||
location['solar_system_name'] = \
|
||||
c.Universe.get_universe_systems_system_id(system_id=location['solar_system_id']).result()['name']
|
||||
if location['station_id']:
|
||||
location['station_name'] = \
|
||||
c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name']
|
||||
elif location['structure_id']:
|
||||
location['station_name'] = \
|
||||
c.Universe.get_universe_structures_structure_id(structure_id=location['structure_id']).result()[
|
||||
'name']
|
||||
if location['station_id']:
|
||||
location['station_name'] = \
|
||||
c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name']
|
||||
elif location['structure_id']:
|
||||
location['station_name'] = \
|
||||
c.Universe.get_universe_structures_structure_id(structure_id=location['structure_id']).result()[
|
||||
'name']
|
||||
else:
|
||||
location['station_name'] = "No Station"
|
||||
|
||||
ship['ship_type_name'] = provider.get_itemtype(ship['ship_type_id']).name
|
||||
|
||||
fat = Fat()
|
||||
fat.system = location['solar_system_name']
|
||||
fat.station = location['station_name']
|
||||
fat.shiptype = ship['ship_type_name']
|
||||
fat.fatlink = fatlink
|
||||
fat.character = character
|
||||
fat.user = request.user
|
||||
|
||||
try:
|
||||
fat.full_clean()
|
||||
fat.save()
|
||||
messages.success(request, _('Fleet participation registered.'))
|
||||
except ValidationError as e:
|
||||
err_messages = []
|
||||
|
||||
for errorname, message in e.message_dict.items():
|
||||
err_messages.append(message[0])
|
||||
|
||||
messages.error(request, ' '.join(err_messages))
|
||||
else:
|
||||
location['station_name'] = "No Station"
|
||||
ship['ship_type_name'] = provider.get_itemtype(ship['ship_type_id']).name
|
||||
context = {
|
||||
'character_id': token.character_id,
|
||||
'character_name': token.character_name,
|
||||
'character_portrait_url': EveCharacter.generic_portrait_url(
|
||||
token.character_id, 128
|
||||
),
|
||||
}
|
||||
|
||||
fat = Fat()
|
||||
fat.system = location['solar_system_name']
|
||||
fat.station = location['station_name']
|
||||
fat.shiptype = ship['ship_type_name']
|
||||
fat.fatlink = fatlink
|
||||
fat.character = character
|
||||
fat.user = request.user
|
||||
try:
|
||||
fat.full_clean()
|
||||
fat.save()
|
||||
messages.success(request, _('Fleet participation registered.'))
|
||||
except ValidationError as e:
|
||||
err_messages = []
|
||||
for errorname, message in e.message_dict.items():
|
||||
err_messages.append(message[0])
|
||||
messages.error(request, ' '.join(err_messages))
|
||||
return render(request, 'fleetactivitytracking/characternotexisting.html', context=context)
|
||||
else:
|
||||
context = {
|
||||
'character_id': token.character_id,
|
||||
'character_name': token.character_name,
|
||||
'character_portrait_url': EveCharacter.generic_portrait_url(
|
||||
token.character_id, 128
|
||||
),
|
||||
}
|
||||
return render(request, 'fleetactivitytracking/characternotexisting.html', context=context)
|
||||
messages.error(request, _('FAT link has expired.'))
|
||||
else:
|
||||
messages.error(request, _('FAT link has expired.'))
|
||||
messages.warning(
|
||||
request,
|
||||
_(
|
||||
f"Cannot register the fleet participation for {character.character_name}. The character needs to be online."
|
||||
),
|
||||
)
|
||||
|
||||
return redirect('fatlink:view')
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{{ group }} {% translate "Audit Log" %}{% endblock page_title %}
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<i class="glyphicon glyphicon-list-alt"></i>
|
||||
</a>
|
||||
|
||||
<a id="clipboard-copy" data-clipboard-text="{{ request.scheme }}://{{request.get_host}}{% url 'groupmanagement:request_add' group.id %}" class="btn btn-warning" title="{% translate "Copy Direct Join Link" %}">
|
||||
<a id="clipboard-copy" data-clipboard-text="{{ SITE_URL }}{% url 'groupmanagement:request_add' group.id %}" class="btn btn-warning" title="{% translate "Copy Direct Join Link" %}">
|
||||
<i class="glyphicon glyphicon-copy"></i>
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -5,7 +5,7 @@ app_name = "groupmanagement"
|
||||
|
||||
urlpatterns = [
|
||||
# groups
|
||||
path("groups", views.groups_view, name="groups"),
|
||||
path("groups/", views.groups_view, name="groups"),
|
||||
path("group/request/join/<int:group_id>/", views.group_request_add, name="request_add"),
|
||||
path(
|
||||
"group/request/leave/<int:group_id>/", views.group_request_leave, name="request_leave"
|
||||
|
||||
@@ -57,7 +57,7 @@ def hr_application_create_view(request, form_id=None):
|
||||
app_form = get_object_or_404(ApplicationForm, id=form_id)
|
||||
if request.method == "POST":
|
||||
if Application.objects.filter(user=request.user).filter(form=app_form).exists():
|
||||
logger.warn(f"User {request.user} attempting to duplicate application to {app_form.corp}")
|
||||
logger.warning(f"User {request.user} attempting to duplicate application to {app_form.corp}")
|
||||
else:
|
||||
application = Application(user=request.user, form=app_form)
|
||||
application.save()
|
||||
@@ -92,7 +92,7 @@ def hr_application_personal_view(request, app_id):
|
||||
}
|
||||
return render(request, 'hrapplications/view.html', context=context)
|
||||
else:
|
||||
logger.warn(f"User {request.user} not authorized to view {app}")
|
||||
logger.warning(f"User {request.user} not authorized to view {app}")
|
||||
return redirect('hrapplications:personal_view')
|
||||
|
||||
|
||||
@@ -105,9 +105,9 @@ def hr_application_personal_removal(request, app_id):
|
||||
logger.info(f"User {request.user} deleting {app}")
|
||||
app.delete()
|
||||
else:
|
||||
logger.warn(f"User {request.user} attempting to delete reviewed app {app}")
|
||||
logger.warning(f"User {request.user} attempting to delete reviewed app {app}")
|
||||
else:
|
||||
logger.warn(f"User {request.user} not authorized to delete {app}")
|
||||
logger.warning(f"User {request.user} not authorized to delete {app}")
|
||||
return redirect('hrapplications:index')
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ def hr_application_view(request, app_id):
|
||||
logger.info(f"Saved comment by user {request.user} to {app}")
|
||||
return redirect('hrapplications:view', app_id)
|
||||
else:
|
||||
logger.warn("User %s does not have permission to add ApplicationComments" % request.user)
|
||||
logger.warning("User %s does not have permission to add ApplicationComments" % request.user)
|
||||
return redirect('hrapplications:view', app_id)
|
||||
else:
|
||||
logger.debug("Returning blank HRApplication comment form.")
|
||||
@@ -171,7 +171,7 @@ def hr_application_approve(request, app_id):
|
||||
app.save()
|
||||
notify(app.user, "Application Accepted", message="Your application to %s has been approved." % app.form.corp, level="success")
|
||||
else:
|
||||
logger.warn(f"User {request.user} not authorized to approve {app}")
|
||||
logger.warning(f"User {request.user} not authorized to approve {app}")
|
||||
return redirect('hrapplications:index')
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ def hr_application_reject(request, app_id):
|
||||
app.save()
|
||||
notify(app.user, "Application Rejected", message="Your application to %s has been rejected." % app.form.corp, level="danger")
|
||||
else:
|
||||
logger.warn(f"User {request.user} not authorized to reject {app}")
|
||||
logger.warning(f"User {request.user} not authorized to reject {app}")
|
||||
return redirect('hrapplications:index')
|
||||
|
||||
|
||||
@@ -208,7 +208,7 @@ def hr_application_search(request):
|
||||
app_list = app_list.filter(
|
||||
form__corp__corporation_id=request.user.profile.main_character.corporation_id)
|
||||
except AttributeError:
|
||||
logger.warn(
|
||||
logger.warning(
|
||||
"User %s missing main character model: unable to filter applications to search" % request.user)
|
||||
|
||||
applications = app_list.filter(
|
||||
@@ -246,6 +246,6 @@ def hr_application_mark_in_progress(request, app_id):
|
||||
app.save()
|
||||
notify(app.user, "Application In Progress", message=f"Your application to {app.form.corp} is being reviewed by {app.reviewer_str}")
|
||||
else:
|
||||
logger.warn(
|
||||
logger.warning(
|
||||
f"User {request.user} unable to mark {app} in progress: already being reviewed by {app.reviewer}")
|
||||
return redirect("hrapplications:view", app_id)
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
allianceauth/locale/uk/LC_MESSAGES/django.mo
Normal file
BIN
allianceauth/locale/uk/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
2410
allianceauth/locale/uk/LC_MESSAGES/django.po
Normal file
2410
allianceauth/locale/uk/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -44,7 +44,7 @@ def notification_view(request, notif_id):
|
||||
notif.mark_viewed()
|
||||
return render(request, 'notifications/view.html', context)
|
||||
else:
|
||||
logger.warn(
|
||||
logger.warning(
|
||||
"User %s not authorized to view notif_id %s belonging to user %s",
|
||||
request.user,
|
||||
notif_id, notif.user
|
||||
|
||||
@@ -34,7 +34,7 @@ class OpTimer(models.Model):
|
||||
fc = models.CharField(max_length=254, default="")
|
||||
post_time = models.DateTimeField(default=timezone.now)
|
||||
eve_character = models.ForeignKey(EveCharacter, null=True,
|
||||
on_delete=models.SET_NULL)
|
||||
on_delete=models.SET_NULL)
|
||||
description = models.TextField(blank=True, default="")
|
||||
type = models.ForeignKey(OpTimerType, null=True, on_delete=models.SET_NULL)
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<h1 class="page-header">{% translate "Permissions Overview" %}</h1>
|
||||
<p>
|
||||
{% if request.GET.all != 'yes' %}
|
||||
{% blocktrans %}Showing only applied permissions{% endblocktrans %}
|
||||
{% blocktranslate %}Showing only applied permissions{% endblocktranslate %}
|
||||
<a href="{% url 'permissions_tool:overview' %}?all=yes" class="btn btn-primary">{% translate "Show All" %}</a>
|
||||
{% else %}
|
||||
{% blocktrans %}Showing all permissions{% endblocktrans %}
|
||||
{% blocktranslate %}Showing all permissions{% endblocktranslate %}
|
||||
<a href="{% url 'permissions_tool:overview' %}?all=no" class="btn btn-primary">{% translate "Show Applied" %}</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
@@ -84,17 +84,17 @@ LOCALE_PATHS = (
|
||||
os.path.join(BASE_DIR, 'locale/'),
|
||||
)
|
||||
|
||||
ugettext = lambda s: s
|
||||
LANGUAGES = (
|
||||
('en', ugettext('English')),
|
||||
('de', ugettext('German')),
|
||||
('es', ugettext('Spanish')),
|
||||
('zh-hans', ugettext('Chinese Simplified')),
|
||||
('ru', ugettext('Russian')),
|
||||
('ko', ugettext('Korean')),
|
||||
('fr', ugettext('French')),
|
||||
('ja', ugettext('Japanese')),
|
||||
('it', ugettext('Italian')),
|
||||
("en", "English"),
|
||||
("de", "German"),
|
||||
("es", "Spanish"),
|
||||
("zh-hans", "Chinese Simplified"),
|
||||
("ru", "Russian"),
|
||||
("ko", "Korean"),
|
||||
("fr", "French"),
|
||||
("ja", "Japanese"),
|
||||
("it", "Italian"),
|
||||
("uk", "Ukrainian"),
|
||||
)
|
||||
|
||||
TEMPLATES = [
|
||||
|
||||
@@ -13,6 +13,13 @@ STATIC_ROOT = "/var/www/{{ project_name }}/static/"
|
||||
# in page titles and the site header.
|
||||
SITE_NAME = '{{ project_name }}'
|
||||
|
||||
# This is your websites URL, set it accordingly
|
||||
# Make sure this URL is WITHOUT a trailing slash
|
||||
SITE_URL = "https://example.com"
|
||||
|
||||
# Django security
|
||||
CSRF_TRUSTED_ORIGINS = [SITE_URL]
|
||||
|
||||
# Change this to enable/disable debug mode, which displays
|
||||
# useful error messages but can leak sensitive data.
|
||||
DEBUG = False
|
||||
@@ -39,15 +46,16 @@ DATABASES['default'] = {
|
||||
|
||||
# Register an application at https://developers.eveonline.com for Authentication
|
||||
# & API Access and fill out these settings. Be sure to set the callback URL
|
||||
# to https://example.com/sso/callback substituting your domain for example.com
|
||||
# to https://example.com/sso/callback substituting your domain for example.com in
|
||||
# CCP's developer portal
|
||||
# Logging in to auth requires the publicData scope (can be overridden through the
|
||||
# LOGIN_TOKEN_SCOPES setting). Other apps may require more (see their docs).
|
||||
ESI_SSO_CLIENT_ID = ''
|
||||
ESI_SSO_CLIENT_SECRET = ''
|
||||
ESI_SSO_CALLBACK_URL = ''
|
||||
ESI_SSO_CALLBACK_URL = f"{SITE_URL}/sso/callback"
|
||||
ESI_USER_CONTACT_EMAIL = '' # A server maintainer that CCP can contact in case of issues.
|
||||
|
||||
# By default emails are validated before new users can log in.
|
||||
# By default, emails are validated before new users can log in.
|
||||
# It's recommended to use a free service like SparkPost or Elastic Email to send email.
|
||||
# https://www.sparkpost.com/docs/integrations/django/
|
||||
# https://elasticemail.com/resources/settings/smtp-api/
|
||||
|
||||
@@ -49,7 +49,7 @@ class DiscourseTasks:
|
||||
DiscourseManager.update_groups(user)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
logger.warn("Discourse group sync failed for %s, retrying in 10 mins" % user)
|
||||
logger.warning("Discourse group sync failed for %s, retrying in 10 mins" % user)
|
||||
raise self.retry(countdown=60 * 10)
|
||||
logger.debug("Updated user %s discourse groups." % user)
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<tr>
|
||||
<td class="text-center">{{ service_name }}</td>
|
||||
<td class="text-center">{{ username }}</td>
|
||||
<td class="text-center"><a href="mumble://{{ service_url }}">{{ service_url }}</a></td>
|
||||
<td class="text-center">
|
||||
{% if username == "" %}
|
||||
<td class="text-center">{{ service_url }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'mumble:activate' %}" title="Activate" class="btn btn-warning">
|
||||
<span class="glyphicon glyphicon-ok"></span>
|
||||
</a>
|
||||
</td>
|
||||
{% else %}
|
||||
<td class="text-center"><a href="mumble://{{ connect_url }}">{{ service_url }}</a></td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'mumble:set_password' %}" title="Set Password" class="btn btn-warning">
|
||||
<span class="glyphicon glyphicon-pencil"></span>
|
||||
</a>
|
||||
@@ -17,9 +20,9 @@
|
||||
<a href="{% url 'mumble:deactivate' %}" title="Deactivate" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-remove"></span>
|
||||
</a>
|
||||
<a href="mumble://{{ connect_url }}" class="btn btn-success" title="Connect">
|
||||
<a href="mumble://{{ connect_url }}" class="btn btn-success" title="Connect">
|
||||
<span class="glyphicon glyphicon-arrow-right"></span>
|
||||
</a>
|
||||
</td>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -176,7 +176,7 @@ class Phpbb3Manager:
|
||||
logger.debug(f"Proceeding to add phpbb user {username_clean} and pwhash starting with {pwhash[0:5]}")
|
||||
# check if the username was simply revoked
|
||||
if Phpbb3Manager.check_user(username_clean):
|
||||
logger.warn("Unable to add phpbb user with username %s - already exists. Updating user instead." % username)
|
||||
logger.warning("Unable to add phpbb user with username %s - already exists. Updating user instead." % username)
|
||||
Phpbb3Manager.__update_user_info(username_clean, email, pwhash)
|
||||
else:
|
||||
try:
|
||||
|
||||
@@ -38,6 +38,12 @@ class SmfService(ServicesHook):
|
||||
if SmfTasks.has_account(user):
|
||||
SmfTasks.update_groups.delay(user.pk)
|
||||
|
||||
def sync_nickname(self, user):
|
||||
logger.debug(f"Updating {self.name} displayed name for {user}")
|
||||
|
||||
if SmfTasks.has_account(user):
|
||||
SmfTasks.update_display_name.apply_async(args=[user.pk], countdown=5) # cooldown on this task to ensure DB clean when syncing
|
||||
|
||||
def update_all_groups(self):
|
||||
logger.debug('Update all %s groups called' % self.name)
|
||||
SmfTasks.update_all_groups.delay()
|
||||
|
||||
@@ -5,11 +5,14 @@ from datetime import datetime
|
||||
import hashlib
|
||||
import logging
|
||||
import re
|
||||
from typing import Tuple
|
||||
|
||||
from packaging import version
|
||||
|
||||
from django.db import connections
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -37,6 +40,10 @@ class SmfManager:
|
||||
|
||||
SQL_DEL_USER = r"DELETE FROM %smembers where member_name = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_UPD_USER = r"UPDATE %smembers SET email_address = %%s, passwd = %%s, real_name = %%s WHERE member_name = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_UPD_DISPLAY_NAME = r"UPDATE %smembers SET real_name = %%s WHERE member_name = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_DIS_USER = r"UPDATE %smembers SET email_address = %%s, passwd = %%s WHERE member_name = %%s" % TABLE_PREFIX
|
||||
|
||||
SQL_USER_ID_FROM_USERNAME = r"SELECT id_member from %smembers WHERE member_name = %%s" % TABLE_PREFIX
|
||||
@@ -174,50 +181,75 @@ class SmfManager:
|
||||
return out
|
||||
|
||||
@classmethod
|
||||
def add_user(cls, username, email_address, groups, characterid):
|
||||
def add_user(cls, username, email_address, groups, main_character: EveCharacter) -> Tuple:
|
||||
"""
|
||||
Add a user to SMF
|
||||
:param username:
|
||||
:param email_address:
|
||||
:param groups:
|
||||
:param main_character:
|
||||
:return:
|
||||
"""
|
||||
|
||||
main_character_id = main_character.character_id
|
||||
main_character_name = main_character.character_name
|
||||
|
||||
logger.debug(
|
||||
f"Adding smf user with member_name {username}, "
|
||||
f"email_address {email_address}, "
|
||||
f"characterid {characterid}"
|
||||
f"Adding smf user with member_name: {username}, "
|
||||
f"email_address: {email_address}, "
|
||||
f"characterid: {main_character_id}, "
|
||||
f"main character: {main_character_name}"
|
||||
)
|
||||
|
||||
cursor = connections['smf'].cursor()
|
||||
username_clean = cls.santatize_username(username)
|
||||
passwd = cls.generate_random_pass()
|
||||
pwhash = cls.gen_hash(username_clean, passwd)
|
||||
logger.debug(f"Proceeding to add smf user {username} and pwhash starting with {pwhash[0:5]}")
|
||||
register_date = cls.get_current_utc_date()
|
||||
|
||||
logger.debug(f"Proceeding to add smf user {username} and pwhash starting with {pwhash[0:5]}")
|
||||
|
||||
# check if the username was simply revoked
|
||||
if cls.check_user(username) is True:
|
||||
logger.warning(f"Unable to add smf user with username {username} - already exists. Updating user instead.")
|
||||
cls.__update_user_info(username_clean, email_address, pwhash)
|
||||
logger.warning(
|
||||
f"Unable to add smf user with username {username} - "
|
||||
f"already exists. Updating user instead."
|
||||
)
|
||||
|
||||
cls.__update_user_info(
|
||||
username_clean, email_address, pwhash, main_character_name
|
||||
)
|
||||
else:
|
||||
try:
|
||||
smf_version = cls._get_current_smf_version()
|
||||
sql_add_user_arguments = [
|
||||
username_clean,
|
||||
pwhash,
|
||||
email_address,
|
||||
register_date,
|
||||
main_character_name,
|
||||
]
|
||||
|
||||
if version.parse(smf_version) < version.parse("2.1"):
|
||||
logger.debug("SMF compatibility: < 2.1")
|
||||
|
||||
cursor.execute(
|
||||
cls.SQL_ADD_USER_SMF_20,
|
||||
[username_clean, pwhash, email_address, register_date, username_clean]
|
||||
)
|
||||
cursor.execute(cls.SQL_ADD_USER_SMF_20, sql_add_user_arguments)
|
||||
else:
|
||||
logger.debug("SMF compatibility: >= 2.1")
|
||||
|
||||
cursor.execute(
|
||||
cls.SQL_ADD_USER_SMF_21,
|
||||
[username_clean, pwhash, email_address, register_date, username_clean]
|
||||
)
|
||||
cls.add_avatar(username_clean, characterid)
|
||||
cursor.execute(cls.SQL_ADD_USER_SMF_21, sql_add_user_arguments)
|
||||
|
||||
cls.add_avatar(username_clean, main_character_id)
|
||||
logger.info(f"Added smf member_name {username_clean}")
|
||||
cls.update_groups(username_clean, groups)
|
||||
except Exception as e:
|
||||
logger.warning(f"Unable to add smf user {username_clean}: {e}")
|
||||
pass
|
||||
|
||||
return username_clean, passwd
|
||||
|
||||
@classmethod
|
||||
def __update_user_info(cls, username, email_address, passwd):
|
||||
def __update_user_info(cls, username, email_address, passwd, main_character_name):
|
||||
logger.debug(
|
||||
f"Updating smf user {username} info: "
|
||||
f"username {email_address} "
|
||||
@@ -225,7 +257,9 @@ class SmfManager:
|
||||
)
|
||||
cursor = connections['smf'].cursor()
|
||||
try:
|
||||
cursor.execute(cls.SQL_DIS_USER, [email_address, passwd, username])
|
||||
cursor.execute(
|
||||
cls.SQL_UPD_USER, [email_address, passwd, main_character_name, username]
|
||||
)
|
||||
logger.info(f"Updated smf user {username} info")
|
||||
except Exception as e:
|
||||
logger.exception(f"Unable to update smf user {username} info. ({e})")
|
||||
@@ -243,6 +277,27 @@ class SmfManager:
|
||||
logger.error(f"Unable to delete smf user {username} - user not found on smf.")
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def update_display_name(cls, user: User):
|
||||
logger.debug(f"Updating SMF displayed name for user {user}")
|
||||
cursor = connections['smf'].cursor()
|
||||
smf_username = user.smf.username
|
||||
|
||||
try:
|
||||
display_name = user.profile.main_character.character_name
|
||||
except Exception as exc:
|
||||
logger.exception(
|
||||
f"Unable to find a main character name for {user}, skipping... ({exc})"
|
||||
)
|
||||
display_name = smf_username
|
||||
|
||||
if cls.check_user(smf_username):
|
||||
cursor.execute(cls.SQL_UPD_DISPLAY_NAME, [display_name, smf_username])
|
||||
logger.info(f"Updated displayed name for smf user {smf_username}")
|
||||
return True
|
||||
logger.error(f"Unable to update smf user {smf_username} - user not found on smf.")
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def update_groups(cls, username, groups):
|
||||
userid = cls.get_user_id(username)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
from django.db import migrations
|
||||
from ..manager import SmfManager
|
||||
|
||||
def on_migrate(apps, schema_editor):
|
||||
SmfUser = apps.get_model("smf", "SmfUser")
|
||||
db_alias = schema_editor.connection.alias
|
||||
all_smf_users = SmfUser.objects.using(db_alias).all()
|
||||
|
||||
for smf_user in all_smf_users:
|
||||
try:
|
||||
auth_user = smf_user.user
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
SmfManager.update_display_name(auth_user)
|
||||
|
||||
def on_migrate_zero(apps, schema_editor):
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('smf', '0002_service_permissions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(on_migrate, on_migrate_zero),
|
||||
]
|
||||
@@ -57,6 +57,40 @@ class SmfTasks:
|
||||
else:
|
||||
logger.debug("User does not have an smf account")
|
||||
|
||||
@staticmethod
|
||||
@shared_task(bind=True, name="smf.update_display_name", base=QueueOnce)
|
||||
def update_display_name(self, pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug(f"Updating SMF displayed name user {user}")
|
||||
|
||||
if SmfTasks.has_account(user):
|
||||
try:
|
||||
if not SmfManager.update_display_name(user):
|
||||
raise Exception("SMF Displayed Name Sync failed")
|
||||
logger.debug(f"Updated user {user} SMF displayed name.")
|
||||
return True
|
||||
except SmfUser.DoesNotExist:
|
||||
logger.info(
|
||||
f"SMF displayed name sync failed for {user}, "
|
||||
"user does not have a SMF account"
|
||||
)
|
||||
except:
|
||||
logger.exception(
|
||||
f"SMF displayed name sync failed for {user}, retrying in 10 mins"
|
||||
)
|
||||
raise self.retry(countdown=60 * 10)
|
||||
else:
|
||||
logger.debug(f"User {user} does not have a SMF account, skipping")
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@shared_task(name="smf.update_all_display_names")
|
||||
def update_all_display_names():
|
||||
logger.debug("Updating ALL SMF display names")
|
||||
for smf_user in SmfUser.objects.exclude(username__exact=''):
|
||||
SmfTasks.update_display_name.delay(smf_user)
|
||||
|
||||
@staticmethod
|
||||
@shared_task(name="smf.update_all_groups")
|
||||
def update_all_groups():
|
||||
|
||||
@@ -19,26 +19,51 @@ ACCESS_PERM = 'smf.access_smf'
|
||||
@login_required
|
||||
@permission_required(ACCESS_PERM)
|
||||
def activate_smf(request):
|
||||
logger.debug("activate_smf called by user %s" % request.user)
|
||||
logger.debug(f"activate_smf called by user {request.user}")
|
||||
# Valid now we get the main characters
|
||||
character = request.user.profile.main_character
|
||||
logger.debug(f"Adding smf user for user {request.user} with main character {character}")
|
||||
result = SmfManager.add_user(SmfTasks.get_username(request.user), request.user.email, ['Member'], character.character_id)
|
||||
main_character = request.user.profile.main_character
|
||||
|
||||
logger.debug(
|
||||
f"Adding smf user for user {request.user} with main character {main_character}"
|
||||
)
|
||||
|
||||
result = SmfManager.add_user(
|
||||
SmfTasks.get_username(request.user),
|
||||
request.user.email,
|
||||
['Member'],
|
||||
main_character,
|
||||
)
|
||||
|
||||
# if empty we failed
|
||||
if result[0] != "":
|
||||
SmfUser.objects.update_or_create(user=request.user, defaults={'username': result[0]})
|
||||
logger.debug("Updated authserviceinfo for user %s with smf credentials. Updating groups." % request.user)
|
||||
SmfUser.objects.update_or_create(
|
||||
user=request.user, defaults={'username': result[0]}
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
f"Updated authserviceinfo for user {request.user} "
|
||||
f"with smf credentials. Updating groups."
|
||||
)
|
||||
|
||||
SmfTasks.update_groups.delay(request.user.pk)
|
||||
logger.info("Successfully activated smf for user %s" % request.user)
|
||||
|
||||
logger.info(f"Successfully activated smf for user {request.user}")
|
||||
|
||||
messages.success(request, _('Activated SMF account.'))
|
||||
credentials = {
|
||||
'username': result[0],
|
||||
'password': result[1],
|
||||
}
|
||||
return render(request, 'services/service_credentials.html', context={'credentials': credentials, 'service': 'SMF'})
|
||||
else:
|
||||
logger.error("Unsuccessful attempt to activate smf for user %s" % request.user)
|
||||
messages.error(request, _('An error occurred while processing your SMF account.'))
|
||||
|
||||
return render(
|
||||
request,
|
||||
'services/service_credentials.html',
|
||||
context={'credentials': credentials, 'service': 'SMF'},
|
||||
)
|
||||
|
||||
logger.error(f"Unsuccessful attempt to activate smf for user {request.user}")
|
||||
messages.error(request, _('An error occurred while processing your SMF account.'))
|
||||
|
||||
return redirect("services:services")
|
||||
|
||||
|
||||
|
||||
@@ -457,7 +457,7 @@ class Teamspeak3AdminTestCase(TestCase):
|
||||
cls.site = AdminSite()
|
||||
cls.admin = AuthTSgroupAdmin(AuthTS, cls.site)
|
||||
cls.group = Group.objects.create(name='test')
|
||||
cls.ts_group = TSgroup.objects.create(ts_group_name='test')
|
||||
cls.ts_group = TSgroup.objects.create(ts_group_id=1, ts_group_name='test')
|
||||
|
||||
def test_field_queryset_no_reserved_names(self):
|
||||
"""Ensure all groups are listed when no reserved names"""
|
||||
|
||||
@@ -44,7 +44,7 @@ def activate_teamspeak3(request):
|
||||
def verify_teamspeak3(request):
|
||||
logger.debug("verify_teamspeak3 called by user %s" % request.user)
|
||||
if not Teamspeak3Tasks.has_account(request.user):
|
||||
logger.warn("Unable to validate user %s teamspeak: no teamspeak data" % request.user)
|
||||
logger.warning("Unable to validate user %s teamspeak: no teamspeak data" % request.user)
|
||||
return redirect("services:services")
|
||||
if request.method == "POST":
|
||||
form = TeamspeakJoinForm(request.POST)
|
||||
|
||||
@@ -2,5 +2,10 @@
|
||||
CSS for allianceauth admin site
|
||||
*/
|
||||
|
||||
.img-circle { border-radius: 50%; }
|
||||
.column-user_profile_pic { width: 50px; }
|
||||
.img-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.column-user_profile_pic {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
|
||||
|
||||
{% block page_title %}
|
||||
{% blocktrans with service_name=view.service_name|title %}Delete {{ service_name }} Account?{% endblocktrans %}
|
||||
{% blocktranslate with service_name=view.service_name|title %}Delete {{ service_name }} Account?{% endblocktranslate %}
|
||||
{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">
|
||||
{% blocktrans with service_name=view.service_name|title %}Delete {{ service_name }} Account?{% endblocktrans %}
|
||||
{% blocktranslate with service_name=view.service_name|title %}Delete {{ service_name }} Account?{% endblocktranslate %}
|
||||
</h1>
|
||||
<div class="container-fluid">
|
||||
<div class="col-md-2 col-md-offset-5">
|
||||
@@ -17,9 +17,9 @@
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
{% blocktrans trimmed with service_name=view.service_name|title %}
|
||||
{% blocktranslate trimmed with service_name=view.service_name|title %}
|
||||
Are you sure you want to delete your {{ service_name }} account {{ object }}?
|
||||
{% endblocktrans %}
|
||||
{% endblocktranslate %}
|
||||
</p>
|
||||
<input class="btn btn-danger btn-block" type="submit" value="Confirm">
|
||||
</form>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% blocktrans with service_name=view.service_name|title %}{{ service_name }} Credentials{% endblocktrans %}{% endblock page_title %}
|
||||
{% block page_title %}{% blocktranslate with service_name=view.service_name|title %}{{ service_name }} Credentials{% endblocktranslate %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktrans with service_name=view.service_name|title %}{{ service_name }} Credentials{% endblocktrans %}</h1>
|
||||
<h1 class="page-header text-center">{% blocktranslate with service_name=view.service_name|title %}{{ service_name }} Credentials{% endblocktranslate %}</h1>
|
||||
<div class="container-fluid">
|
||||
<div class="col-lg-4 col-lg-offset-4">
|
||||
<form class="form-signin">
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% blocktrans with service_name=view.service_name|title %}{{ service_name }} Password Change{% endblocktrans %}{% endblock page_title %}
|
||||
{% block page_title %}{% blocktranslate with service_name=view.service_name|title %}{{ service_name }} Password Change{% endblocktranslate %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktrans with service_name=view.service_name|title %}Set {{service_name}} Password{% endblocktrans %}</h1>
|
||||
<h1 class="page-header text-center">{% blocktranslate with service_name=view.service_name|title %}Set {{service_name}} Password{% endblocktranslate %}</h1>
|
||||
<div class="container-fluid">
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
<div class="row">
|
||||
|
||||
@@ -23,9 +23,10 @@
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit">{% translate "Create SRP Fleet" %}</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="alert alert-info" role="alert">{% blocktrans %}Give this link to the line members{% endblocktrans %}.</div>
|
||||
<div class="alert alert-info" role="alert">{% blocktranslate %}Give this link to the line members{% endblocktranslate %}.</div>
|
||||
<div class="alert alert-info" role="alert">
|
||||
{{ request.scheme }}://{{ request.get_host }}{% url 'srp:request' completed_srp_code %}</div>
|
||||
{{ SITE_URL }}{% url 'srp:request' completed_srp_code %}
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<a href="{% url 'srp:management' %}" class="btn btn-primary btn-lg">{% translate "Continue" %}</a>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% load humanize %}
|
||||
@@ -7,7 +6,7 @@
|
||||
{% block extra_css %}
|
||||
{% include 'bundles/datatables-css.html' %}
|
||||
{% include 'bundles/x-editable.css.html' %}
|
||||
<link href="{% static 'allianceauth/css/checkbox.css' %}" rel="stylesheet">
|
||||
{% include 'bundles/checkbox-css.html' %}
|
||||
<style>
|
||||
.copy-text-fa-icon:hover {
|
||||
cursor: pointer;
|
||||
@@ -93,9 +92,9 @@
|
||||
<th class="text-center">{% translate "Ship Type" %}</th>
|
||||
<th class="text-center">{% translate "Killboard Loss Amt" %}</th>
|
||||
<th class="text-center">{% translate "SRP ISK Cost" %}
|
||||
<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="{% blocktrans trimmed %}Click value to edit
|
||||
<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="{% blocktranslate trimmed %}Click value to edit
|
||||
Enter to save & next
|
||||
ESC to cancel{% endblocktrans %}" id="blah"></i></th>
|
||||
ESC to cancel{% endblocktranslate %}" id="blah"></i></th>
|
||||
<th class="text-center">{% translate "Post Time" %}</th>
|
||||
<th class="text-center">{% translate "Status" %}</th>
|
||||
{% if perms.auth.srp_management %}
|
||||
|
||||
@@ -11,7 +11,7 @@ urlpatterns = [
|
||||
path('<int:fleet_id>/view/', views.srp_fleet_view, name='fleet'),
|
||||
path('add/', views.srp_fleet_add_view, name='add'),
|
||||
path('<int:fleet_id>/edit/', views.srp_fleet_edit_view, name='edit'),
|
||||
path('<str:fleet_srp>/request', views.srp_request_view, name='request'),
|
||||
path('<str:fleet_srp>/request/', views.srp_request_view, name='request'),
|
||||
|
||||
# SRP URLS
|
||||
path('<int:fleet_id>/remove/', views.srp_fleet_remove, name='remove'),
|
||||
@@ -27,6 +27,6 @@ urlpatterns = [
|
||||
name='request_approve'),
|
||||
path('request/reject/', views.srp_request_reject,
|
||||
name='request_reject'),
|
||||
path('request/<int:fleet_srp_request_id>/update', views.srp_request_update_amount,
|
||||
path('request/<int:fleet_srp_request_id>/update/', views.srp_request_update_amount,
|
||||
name="request_update_amount"),
|
||||
]
|
||||
|
||||
@@ -2,17 +2,9 @@ body {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.gray-icon-color .fa {
|
||||
color: #505050;
|
||||
}
|
||||
|
||||
.notification-bell-color {
|
||||
color: #a88f1e;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
height: 58px;
|
||||
padding: 19px 19px;
|
||||
padding: 19px;
|
||||
}
|
||||
|
||||
.auth-navbar-top {
|
||||
@@ -48,36 +40,36 @@ ul.list-group.list-group-horizontal > li.list-group-item {
|
||||
}
|
||||
|
||||
@media all {
|
||||
/* style nav tabs in dark mode*/
|
||||
/* style nav tabs in dark mode */
|
||||
.template-dark-mode .nav-tabs > li.active > a {
|
||||
background-color: rgb(70, 69, 69) !important;
|
||||
color: rgb(255, 255, 255) !important;
|
||||
background-color: rgb(70 69 69) !important;
|
||||
color: rgb(255 255 255) !important;
|
||||
}
|
||||
|
||||
.panel-tabs-aa {
|
||||
border-top: none;
|
||||
border-top-left-radius: 0%;
|
||||
border-top-right-radius: 0%;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
/* style group headers within a table */
|
||||
.template-light-mode .tr-group {
|
||||
background-color: #e6e6e6 !important;
|
||||
background-color: rgb(230 230 230) !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.template-dark-mode .tr-group {
|
||||
background-color: rgb(105, 105, 105) !important;
|
||||
background-color: rgb(105 105 105) !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* default style for tables */
|
||||
.template-light-mode .table-aa > thead > tr > th {
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
border-bottom: 1px solid rgb(242 242 242);
|
||||
}
|
||||
|
||||
.template-dark-mode .table-aa > thead > tr > th {
|
||||
border-bottom: 1px solid rgb(70, 69, 69);
|
||||
border-bottom: 1px solid rgb(70 69 69);
|
||||
}
|
||||
|
||||
.table-aa > thead > tr > th {
|
||||
@@ -85,11 +77,11 @@ ul.list-group.list-group-horizontal > li.list-group-item {
|
||||
}
|
||||
|
||||
.template-light-mode .table-aa > tbody > tr > td {
|
||||
border-bottom: 1px solid #f2f2f2;
|
||||
border-bottom: 1px solid rgb(242 242 242);
|
||||
}
|
||||
|
||||
.template-dark-mode .table-aa > tbody > tr > td {
|
||||
border-bottom: 1px solid rgb(70, 69, 69);
|
||||
border-bottom: 1px solid rgb(70 69 69);
|
||||
}
|
||||
|
||||
.table-aa > tbody > tr > td {
|
||||
@@ -100,9 +92,9 @@ ul.list-group.list-group-horizontal > li.list-group-item {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.task-status-progress-bar {
|
||||
font-size: 15px!important;
|
||||
line-height: normal!important;
|
||||
.progress .progress-bar {
|
||||
font-size: 13px;
|
||||
line-height: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,11 +102,11 @@ ul.list-group.list-group-horizontal > li.list-group-item {
|
||||
------------------------------------------------------------------------------------- */
|
||||
@media all {
|
||||
.template-light-mode .nav-pills > li > a.active {
|
||||
background-color: rgb(236, 240, 241);
|
||||
background-color: rgb(236 240 241);
|
||||
}
|
||||
|
||||
.template-dark-mode .nav-pills > li > a.active {
|
||||
background-color: rgb(48, 48, 48);
|
||||
background-color: rgb(48 48 48);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,45 +128,41 @@ ul.list-group.list-group-horizontal > li.list-group-item {
|
||||
|
||||
.dropdown-menu > li > a {
|
||||
clear: both;
|
||||
color: rgb(123, 138, 139);
|
||||
color: rgb(123 138 139);
|
||||
display: block;
|
||||
font-weight: 400;
|
||||
line-height: 1.42857143;
|
||||
line-height: 1.42857;
|
||||
padding: 3px 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.top-user-menu {
|
||||
color: rgb(255, 255, 255);
|
||||
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;
|
||||
margin: 7.5px 5px;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
.navbar-nav .open .dropdown-menu .dropdown-header, .navbar-nav .open .dropdown-menu > li > a {
|
||||
.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);
|
||||
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;
|
||||
@@ -188,7 +176,7 @@ ul.list-group.list-group-horizontal > li.list-group-item {
|
||||
------------------------------------------------------------------------------------- */
|
||||
@media all {
|
||||
.nav-item-eve-time .eve-time-wrapper {
|
||||
color: rgb(255, 255, 255);
|
||||
color: rgb(255 255 255);
|
||||
display: block;
|
||||
line-height: 21px;
|
||||
padding: 10px 15px;
|
||||
@@ -203,26 +191,17 @@ ul.list-group.list-group-horizontal > li.list-group-item {
|
||||
}
|
||||
}
|
||||
|
||||
/* Small devices (tablets, 768px and up) */
|
||||
/* 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-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.row.vertical-flexbox-row2 > [class*='col-'] {
|
||||
.row.vertical-flexbox-row2 > [class*="col-"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra Small devices (Phones, <768px) */
|
||||
@media (max-width: 767px) {
|
||||
.button-wrapper .btn {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
.checkbox label:after,
|
||||
.radio label:after {
|
||||
content: '';
|
||||
display: table;
|
||||
.checkbox label::after,
|
||||
.radio label::after {
|
||||
clear: both;
|
||||
content: "";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.checkbox .cr,
|
||||
.radio .cr {
|
||||
position: relative;
|
||||
border: 1px solid rgb(169 169 169);
|
||||
border-radius: 0.25em;
|
||||
display: inline-block;
|
||||
border: 1px solid #a9a9a9;
|
||||
border-radius: .25em;
|
||||
width: 1.3em;
|
||||
height: 1.3em;
|
||||
float: left;
|
||||
margin-right: .5em;
|
||||
height: 1.3em;
|
||||
margin-right: 0.5em;
|
||||
position: relative;
|
||||
width: 1.3em;
|
||||
}
|
||||
|
||||
.radio .cr {
|
||||
@@ -23,11 +23,11 @@
|
||||
|
||||
.checkbox .cr .cr-icon,
|
||||
.radio .cr .cr-icon {
|
||||
position: absolute;
|
||||
font-size: .8em;
|
||||
line-height: 0;
|
||||
top: 50%;
|
||||
font-size: 0.8em;
|
||||
left: 20%;
|
||||
line-height: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
.radio .cr .cr-icon {
|
||||
@@ -41,18 +41,18 @@
|
||||
|
||||
.checkbox label input[type="checkbox"] + .cr > .cr-icon,
|
||||
.radio label input[type="radio"] + .cr > .cr-icon {
|
||||
transform: scale(3) rotateZ(-20deg);
|
||||
opacity: 0;
|
||||
transition: all .3s ease-in;
|
||||
transform: scale(3) rotateZ(-20deg);
|
||||
transition: all 0.3s ease-in;
|
||||
}
|
||||
|
||||
.checkbox label input[type="checkbox"]:checked + .cr > .cr-icon,
|
||||
.radio label input[type="radio"]:checked + .cr > .cr-icon {
|
||||
transform: scale(1) rotateZ(0deg);
|
||||
opacity: 1;
|
||||
transform: scale(1) rotateZ(0deg);
|
||||
}
|
||||
|
||||
.checkbox label input[type="checkbox"]:disabled + .cr,
|
||||
.radio label input[type="radio"]:disabled + .cr {
|
||||
opacity: .5;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
aria-valuenow="{% decimal_widthratio tasks_count tasks_total 100 %}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
style="width: {% decimal_widthratio tasks_count tasks_total 100 %}%;">
|
||||
<p style="margin-top:5px;">{% widthratio tasks_count tasks_total 100 %}%</p>
|
||||
style="width: {% decimal_widthratio tasks_count tasks_total 100 %}%;"
|
||||
>
|
||||
<span>{% widthratio tasks_count tasks_total 100 %}%</span>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load navactive %}
|
||||
{% load auth_notifications %}
|
||||
@@ -16,8 +15,7 @@
|
||||
|
||||
{% include 'bundles/bootstrap-css.html' %}
|
||||
{% include 'bundles/fontawesome.html' %}
|
||||
|
||||
<link href="{% static 'allianceauth/css/auth-base.css' %}" rel="stylesheet">
|
||||
{% include 'bundles/auth-base-css.html' %}
|
||||
|
||||
{% block extra_css %}{% endblock extra_css %}
|
||||
</head>
|
||||
|
||||
@@ -53,6 +53,14 @@
|
||||
<!-- logout / login -->
|
||||
<li role="separator" class="divider"></li>
|
||||
{% if user.is_authenticated %}
|
||||
<li>
|
||||
<a href="{% url 'authentication:token_management' %}">
|
||||
<i class="fas fa-user-lock"></i>
|
||||
{% translate "Token Management" %}
|
||||
</a>
|
||||
</li>
|
||||
<li role="separator" class="divider"></li>
|
||||
|
||||
<li><a href="{% url 'logout' %}">{% translate "Logout" %}</a></li>
|
||||
{% else %}
|
||||
<li><a href="{% url 'authentication:login' %}">{% translate "Login" %}</a></li>
|
||||
|
||||
3
allianceauth/templates/bundles/auth-base-css.html
Normal file
3
allianceauth/templates/bundles/auth-base-css.html
Normal file
@@ -0,0 +1,3 @@
|
||||
{% load static %}
|
||||
|
||||
<link href="{% static 'allianceauth/css/auth-base.css' %}" rel="stylesheet">
|
||||
3
allianceauth/templates/bundles/checkbox-css.html
Normal file
3
allianceauth/templates/bundles/checkbox-css.html
Normal file
@@ -0,0 +1,3 @@
|
||||
{% load static %}
|
||||
|
||||
<link href="{% static 'allianceauth/css/checkbox.css' %}" rel="stylesheet">
|
||||
@@ -1,3 +1,3 @@
|
||||
<!-- Start X-editable JS from cdnjs -->
|
||||
<<script src="https://cdnjs.cloudflare.com/ajax/libs/x-editable/1.5.1/bootstrap3-editable/js/bootstrap-editable.min.js" integrity="sha512-Mvqhe3YIUElH6VT0CFmUeRgYMrLvCGd2mvYCnJOf2nL9FvRBK74qRgTn7u0zSqA5cHiGxy83bwuhl1ASbS9M/w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/x-editable/1.5.1/bootstrap3-editable/js/bootstrap-editable.min.js" integrity="sha512-Mvqhe3YIUElH6VT0CFmUeRgYMrLvCGd2mvYCnJOf2nL9FvRBK74qRgTn7u0zSqA5cHiGxy83bwuhl1ASbS9M/w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<!-- End X-editable JS from cdnjs -->
|
||||
|
||||
@@ -4,8 +4,7 @@ from typing import Optional
|
||||
import amqp.exceptions
|
||||
import requests
|
||||
from celery.app import app_or_default
|
||||
from packaging.version import InvalidVersion
|
||||
from packaging.version import Version as Pep440Version
|
||||
from packaging.version import InvalidVersion, Version as Pep440Version
|
||||
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="row">
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<p>{% blocktrans %}Are you sure you want to delete timer "{{ object }}"?{% endblocktrans %}</p>
|
||||
<p>{% blocktranslate %}Are you sure you want to delete timer "{{ object }}"?{% endblocktranslate %}</p>
|
||||
<input class="btn btn-danger btn-block" type="submit" value="Confirm">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% load evelinks %}
|
||||
|
||||
25
allianceauth/utils/django.py
Normal file
25
allianceauth/utils/django.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import sys
|
||||
from copy import copy
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class StartupCommand:
|
||||
"""Information about the command this Django instance was started with."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._argv = copy(sys.argv)
|
||||
|
||||
@property
|
||||
def argv(self) -> list:
|
||||
"""Return raw list of command line arguments."""
|
||||
return self._argv
|
||||
|
||||
@property
|
||||
def script_name(self) -> str:
|
||||
"""Return the base script name."""
|
||||
path = Path(self._argv[0])
|
||||
return path.name
|
||||
|
||||
@property
|
||||
def is_management_command(self) -> bool:
|
||||
return self.script_name == "manage.py"
|
||||
25
allianceauth/utils/tests/test_django.py
Normal file
25
allianceauth/utils/tests/test_django.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from allianceauth.utils.django import StartupCommand
|
||||
|
||||
MODULE_PATH = "allianceauth.utils.django"
|
||||
|
||||
|
||||
class TestStartupCommand(TestCase):
|
||||
def test_should_detect_management_command(self):
|
||||
# when
|
||||
with patch(MODULE_PATH + ".sys") as m:
|
||||
m.argv = ["manage.py", "check"]
|
||||
info = StartupCommand()
|
||||
# then
|
||||
self.assertTrue(info.is_management_command)
|
||||
|
||||
def test_should_detect_not_a_management_command(self):
|
||||
# when
|
||||
with patch(MODULE_PATH + ".sys") as m:
|
||||
m.argv = ['/home/python/allianceauth-dev/venv/bin/gunicorn', 'myauth.wsgi']
|
||||
info = StartupCommand()
|
||||
# then
|
||||
self.assertFalse(info.is_management_command)
|
||||
@@ -1,7 +1,7 @@
|
||||
PROTOCOL=https://
|
||||
AUTH_SUBDOMAIN=%AUTH_SUBDOMAIN%
|
||||
DOMAIN=%DOMAIN%
|
||||
AA_DOCKER_TAG=registry.gitlab.com/allianceauth/allianceauth/auth:v3.0.0
|
||||
AA_DOCKER_TAG=registry.gitlab.com/allianceauth/allianceauth/auth:v3.5.1
|
||||
|
||||
# Nginx Proxy Manager
|
||||
PROXY_HTTP_PORT=80
|
||||
@@ -21,6 +21,7 @@ AA_DB_NAME=alliance_auth
|
||||
AA_DB_USER=aauth
|
||||
AA_DB_PASSWORD=%AA_DB_PASSWORD%
|
||||
AA_DB_ROOT_PASSWORD=%AA_DB_ROOT_PASSWORD%
|
||||
AA_DB_CHARSET=utf8mb4
|
||||
AA_EMAIL_HOST=''
|
||||
AA_EMAIL_PORT=587
|
||||
AA_EMAIL_HOST_USER=''
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
FROM python:3.9-slim
|
||||
ARG AUTH_VERSION=v3.0.0
|
||||
ARG AUTH_VERSION=v3.5.1
|
||||
ARG AUTH_PACKAGE=allianceauth==${AUTH_VERSION}
|
||||
ENV VIRTUAL_ENV=/opt/venv
|
||||
ENV AUTH_USER=allianceauth
|
||||
@@ -21,7 +21,7 @@ RUN mkdir -p ${VIRTUAL_ENV} \
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt-get update && apt-get upgrade -y && apt-get install -y \
|
||||
libmariadb-dev gcc supervisor git htop
|
||||
libmariadb-dev gcc supervisor git htop pkg-config
|
||||
|
||||
# Switch to non-root user
|
||||
USER ${AUTH_USER}
|
||||
|
||||
@@ -49,7 +49,11 @@ Using a custom docker image is the preferred approach, as it gives you the stabi
|
||||
|
||||
1. Add each additional package that you want to install to a single line in `conf/requirements.txt`. It is recommended, but not required, that you include a version number as well. This will keep your packages from magically updating. You can lookup packages on https://package.wiki, and copy everything after `pip install` from the top of the page to use the most recent version. It should look something like `allianceauth-signal-pings==0.0.7`. Every entry in this file should be on a separate line
|
||||
1. In `docker-compose.yml`, comment out the `image` line under `allianceauth` (line 36... ish) and uncomment the `build` section
|
||||
1. Now run `docker-compose --env-file=.env up -d`, your custom container will be built, and auth will have your new packages. Make sure to follow the package's instructions on config values that go in `local.py`
|
||||
1. run `docker-compose --env-file=.env up -d`, your custom container will be built, and auth will have your new packages. Make sure to follow the package's instructions on config values that go in `local.py`
|
||||
1. run `docker-compose exec allianceauth bash` to open up a terminal inside your auth container
|
||||
1. run `allianceauth update myauth`
|
||||
1. run `auth migrate`
|
||||
1. run `auth collectstatic`
|
||||
|
||||
_NOTE: It is recommended that you put any secret values (API keys, database credentials, etc) in an environment variable instead of hardcoding them into `local.py`. This gives you the ability to track your config in git without committing passwords. To do this, just add it to your `.env` file, and then reference in `local.py` with `os.environ.get("SECRET_NAME")`_
|
||||
|
||||
@@ -58,8 +62,12 @@ _NOTE: It is recommended that you put any secret values (API keys, database cred
|
||||
### Base Image
|
||||
Whether you're using a custom image or not, the version of auth is dictated by $AA_DOCKER_TAG in your `.env` file.
|
||||
1. To update to a new version of auth, update the version number at the end (or replace the whole value with the tag in the release notes).
|
||||
1. Next, run `docker-compose pull`
|
||||
1. Finally, run `docker-compose --env-file=.env up -d`
|
||||
1. run `docker-compose pull`
|
||||
1. run `docker-compose --env-file=.env up -d`
|
||||
1. run `docker-compose exec allianceauth bash` to open up a terminal inside your auth container
|
||||
1. run `allianceauth update myauth`
|
||||
1. run `auth migrate`
|
||||
1. run `auth collectstatic`
|
||||
|
||||
_NOTE: If you specify a version of allianceauth in your `requirements.txt` in a custom image it will override the version from the base image. Not recommended unless you know what you're doing_
|
||||
|
||||
|
||||
@@ -3,14 +3,23 @@ from .base import *
|
||||
|
||||
SECRET_KEY = os.environ.get("AA_SECRET_KEY")
|
||||
SITE_NAME = os.environ.get("AA_SITENAME")
|
||||
SITE_URL = (
|
||||
f"{os.environ.get('PROTOCOL')}"
|
||||
f"{os.environ.get('AUTH_SUBDOMAIN')}."
|
||||
f"{os.environ.get('DOMAIN')}"
|
||||
)
|
||||
CSRF_TRUSTED_ORIGINS = [SITE_URL]
|
||||
DEBUG = os.environ.get("AA_DEBUG", False)
|
||||
DATABASES['default'] = {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': os.environ.get("AA_DB_NAME"),
|
||||
'USER': os.environ.get("AA_DB_USER"),
|
||||
'PASSWORD': os.environ.get("AA_DB_PASSWORD"),
|
||||
'HOST': os.environ.get("AA_DB_HOST"),
|
||||
'PORT': os.environ.get("AA_DB_PORT", "3306"),
|
||||
DATABASES["default"] = {
|
||||
"ENGINE": "django.db.backends.mysql",
|
||||
"NAME": os.environ.get("AA_DB_NAME"),
|
||||
"USER": os.environ.get("AA_DB_USER"),
|
||||
"PASSWORD": os.environ.get("AA_DB_PASSWORD"),
|
||||
"HOST": os.environ.get("AA_DB_HOST"),
|
||||
"PORT": os.environ.get("AA_DB_PORT", "3306"),
|
||||
"OPTIONS": {
|
||||
"charset": os.environ.get("AA_DB_CHARSET", "utf8mb4")
|
||||
}
|
||||
}
|
||||
|
||||
# Register an application at https://developers.eveonline.com for Authentication
|
||||
@@ -21,10 +30,10 @@ DATABASES['default'] = {
|
||||
|
||||
ESI_SSO_CLIENT_ID = os.environ.get("ESI_SSO_CLIENT_ID")
|
||||
ESI_SSO_CLIENT_SECRET = os.environ.get("ESI_SSO_CLIENT_SECRET")
|
||||
ESI_SSO_CALLBACK_URL = (f"{os.environ.get('PROTOCOL')}"
|
||||
f"{os.environ.get('AUTH_SUBDOMAIN')}."
|
||||
f"{os.environ.get('DOMAIN')}/sso/callback")
|
||||
ESI_USER_CONTACT_EMAIL = os.environ.get("ESI_USER_CONTACT_EMAIL") # A server maintainer that CCP can contact in case of issues.
|
||||
ESI_SSO_CALLBACK_URL = f"{SITE_URL}/sso/callback"
|
||||
ESI_USER_CONTACT_EMAIL = os.environ.get(
|
||||
"ESI_USER_CONTACT_EMAIL"
|
||||
) # A server maintainer that CCP can contact in case of issues.
|
||||
|
||||
# By default emails are validated before new users can log in.
|
||||
# It's recommended to use a free service like SparkPost or Elastic Email to send email.
|
||||
@@ -40,40 +49,40 @@ EMAIL_HOST_PASSWORD = os.environ.get("AA_EMAIL_HOST_PASSWORD", "")
|
||||
EMAIL_USE_TLS = os.environ.get("AA_EMAIL_USE_TLS", True)
|
||||
DEFAULT_FROM_EMAIL = os.environ.get("AA_DEFAULT_FROM_EMAIL", "")
|
||||
|
||||
ROOT_URLCONF = 'myauth.urls'
|
||||
WSGI_APPLICATION = 'myauth.wsgi.application'
|
||||
ROOT_URLCONF = "myauth.urls"
|
||||
WSGI_APPLICATION = "myauth.wsgi.application"
|
||||
STATIC_ROOT = "/var/www/myauth/static/"
|
||||
BROKER_URL = F"redis://{os.environ.get('AA_REDIS', 'redis:6379')}/0"
|
||||
CELERY_RESULT_BACKEND = F"redis://{os.environ.get('AA_REDIS', 'redis:6379')}/0"
|
||||
BROKER_URL = f"redis://{os.environ.get('AA_REDIS', 'redis:6379')}/0"
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "redis_cache.RedisCache",
|
||||
"LOCATION": os.environ.get('AA_REDIS', 'redis:6379'),
|
||||
"OPTIONS": {
|
||||
"DB": 1,
|
||||
}
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": f"redis://{os.environ.get('AA_REDIS', 'redis:6379')}/1", # change the 1 here to change the database used
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Add any additional apps to this list.
|
||||
INSTALLED_APPS += [
|
||||
# https://allianceauth.readthedocs.io/en/latest/features/apps/index.html
|
||||
# 'allianceauth.corputils',
|
||||
# 'allianceauth.fleetactivitytracking',
|
||||
# 'allianceauth.optimer',
|
||||
# 'allianceauth.permissions_tool',
|
||||
# 'allianceauth.srp',
|
||||
# 'allianceauth.timerboard',
|
||||
# https://allianceauth.readthedocs.io/en/latest/features/apps/index.html
|
||||
# 'allianceauth.corputils',
|
||||
# 'allianceauth.fleetactivitytracking',
|
||||
# 'allianceauth.optimer',
|
||||
# 'allianceauth.permissions_tool',
|
||||
# 'allianceauth.srp',
|
||||
# 'allianceauth.timerboard',
|
||||
|
||||
# https://allianceauth.readthedocs.io/en/latest/features/services/index.html
|
||||
# 'allianceauth.services.modules.discord',
|
||||
# 'allianceauth.services.modules.discourse',
|
||||
# 'allianceauth.services.modules.ips4',
|
||||
# 'allianceauth.services.modules.openfire',
|
||||
# 'allianceauth.services.modules.phpbb3',
|
||||
# 'allianceauth.services.modules.smf',
|
||||
# 'allianceauth.services.modules.teamspeak3',
|
||||
# 'allianceauth.services.modules.xenforo',
|
||||
# https://allianceauth.readthedocs.io/en/latest/features/services/index.html
|
||||
# 'allianceauth.services.modules.discord',
|
||||
# 'allianceauth.services.modules.discourse',
|
||||
# 'allianceauth.services.modules.ips4',
|
||||
# 'allianceauth.services.modules.openfire',
|
||||
# 'allianceauth.services.modules.mumble',
|
||||
# An example of running mumble with authenticator in docker can be found here
|
||||
# https://github.com/Solar-Helix-Independent-Transport/allianceauth-docker-mumble
|
||||
# 'allianceauth.services.modules.phpbb3',
|
||||
# 'allianceauth.services.modules.smf',
|
||||
# 'allianceauth.services.modules.teamspeak3',
|
||||
# 'allianceauth.services.modules.xenforo',
|
||||
]
|
||||
|
||||
#######################################
|
||||
|
||||
@@ -4,5 +4,3 @@ FROM $AA_DOCKER_TAG
|
||||
RUN cd /home/allianceauth
|
||||
COPY /conf/requirements.txt requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
RUN python $AUTH_HOME/myauth/manage.py collectstatic --noinput
|
||||
RUN allianceauth update myauth
|
||||
|
||||
@@ -52,12 +52,19 @@ services:
|
||||
- auth_mysql
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana-oss:8.3.2
|
||||
image: grafana/grafana-oss:8.5.22
|
||||
restart: always
|
||||
depends_on:
|
||||
- auth_mysql
|
||||
volumes:
|
||||
- ./grafana-datasource.yml:/etc/grafana/provisioning/datasources/datasource.yaml
|
||||
- ./grafana-dashboards.yml:/etc/grafana/provisioning/dashboards/datasource.yaml
|
||||
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
|
||||
- grafana-data:/var/lib/grafana
|
||||
environment:
|
||||
GF_INSTALL_PLUGINS: grafana-piechart-panel,grafana-clock-panel,grafana-simple-json-datasource
|
||||
GF_AUTH_DATABASE_PASSWORD: ${GRAFANA_DB_PASSWORD}
|
||||
|
||||
proxy:
|
||||
image: 'jc21/nginx-proxy-manager:latest'
|
||||
restart: always
|
||||
|
||||
25
docker/grafana-dashboards.yml
Normal file
25
docker/grafana-dashboards.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
# <string> an unique provider name
|
||||
- name: 'auth dashboards'
|
||||
# <int> org id. will default to orgId 1 if not specified
|
||||
orgId: 1
|
||||
# <string, required> name of the dashboard folder. Required
|
||||
folder: ''
|
||||
# <string> folder UID. will be automatically generated if not specified
|
||||
folderUid: ''
|
||||
# <string, required> provider type. Required
|
||||
type: file
|
||||
# <bool> disable dashboard deletion
|
||||
disableDeletion: false
|
||||
# <bool> enable dashboard editing
|
||||
editable: true
|
||||
# <int> how often Grafana will scan for changed dashboards
|
||||
updateIntervalSeconds: 10
|
||||
# <bool> allow updating provisioned dashboards from the UI
|
||||
allowUiUpdates: false
|
||||
options:
|
||||
# <string, required> path to dashboard files on disk. Required
|
||||
path: /var/lib/grafana/dashboards
|
||||
foldersFromFilesStructure: true
|
||||
12
docker/grafana-datasource.yml
Normal file
12
docker/grafana-datasource.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
|
||||
- name: MySQL
|
||||
type: mysql
|
||||
url: auth_mysql
|
||||
database: alliance_auth
|
||||
user: grafana
|
||||
editable: true
|
||||
secureJsonData:
|
||||
password: ${GF_AUTH_DATABASE_PASSWORD}
|
||||
@@ -46,6 +46,7 @@ extensions = [
|
||||
'recommonmark',
|
||||
'sphinxcontrib_django2',
|
||||
'sphinx.ext.viewcode',
|
||||
'sphinx_copybutton'
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user