From 13b0dbc96040b138e20603196175c00669d94b07 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Sat, 25 Mar 2017 16:28:26 -0400 Subject: [PATCH] Restructure settings.py.example Add help text to State model Remove navbar group headings Fix registration email pluralization Group memberships on state admin page Attempt to prevent resetting of state if set on profile admin manually Embed readthedocs on help page Rename CorpStats API Index to Registration Index Default corputils view to main character's corp if available Correct Application characters listing Correct string coercion of optimers Improve readability of SRP values with intcomma Beautify tables by embeding in panels Replace slugify with py3-friendly python-slugify --- alliance_auth/settings.py.example | 128 ++++++---- authentication/admin.py | 31 ++- .../migrations/0015_user_profiles.py | 23 +- authentication/models.py | 19 +- authentication/signals.py | 7 + authentication/templates/registered/base.html | 7 - .../registration/activation_email.txt | 2 +- authentication/urls.py | 2 +- authentication/views.py | 2 - corputils/templates/corputils/corpstats.html | 2 +- corputils/views.py | 6 + docs/features/corpstats.md | 8 +- fleetactivitytracking/forms.py | 9 - hrapplications/admin.py | 6 +- hrapplications/models.py | 2 +- optimer/models.py | 3 +- requirements.txt | 2 +- srp/views.py | 64 ++--- .../templates/registered/groupmanagement.html | 5 +- stock/templates/registered/groupmembers.html | 1 + .../templates/registered/groupmembership.html | 1 + stock/templates/registered/help.html | 9 +- .../registered/hrapplicationmanagement.html | 238 +++++++++--------- .../registered/hrapplicationview.html | 44 ++-- .../registered/operationmanagement.html | 4 +- .../templates/registered/srpfleetrequest.html | 27 +- 26 files changed, 360 insertions(+), 292 deletions(-) diff --git a/alliance_auth/settings.py.example b/alliance_auth/settings.py.example index 38c19c58..17290bab 100644 --- a/alliance_auth/settings.py.example +++ b/alliance_auth/settings.py.example @@ -17,31 +17,6 @@ import djcelery from django.contrib import messages from celery.schedules import crontab -djcelery.setup_loader() - -# Celery configuration -BROKER_URL = 'redis://localhost:6379/0' -CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler" -CELERYBEAT_SCHEDULE = dict() - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = [] - - -# Application definition - INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', @@ -68,7 +43,7 @@ INSTALLED_APPS = [ 'geelweb.django.navhelper', 'bootstrap_pagination', - # Services + # Services - comment out if not used 'services.modules.mumble', 'services.modules.discord', 'services.modules.discourse', @@ -83,6 +58,25 @@ INSTALLED_APPS = [ 'services.modules.teamspeak3', ] +##################################################### +## +## Django Project Configuration +## +##################################################### +# You shouldn't need to touch most of this. +# Scroll down for Auth Configuration +##################################################### + +djcelery.setup_loader() + +# Celery configuration +BROKER_URL = 'redis://localhost:6379/0' +CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler" +CELERYBEAT_SCHEDULE = dict() + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -137,20 +131,6 @@ TEMPLATES = [ WSGI_APPLICATION = 'alliance_auth.wsgi.application' -# Database -# https://docs.djangoproject.com/en/1.10/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'alliance_auth', - 'USER': 'allianceserver', - 'PASSWORD': '', - 'HOST': '127.0.0.1', - 'PORT': '3306', - }, -} - # Password validation # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators @@ -213,20 +193,46 @@ CACHES = { ##################################################### ## -## Auth configuration starts here +## Auth Configuration ## ##################################################### -#################### +############################### +# Required Django Settings +############################### +# SECRET_KEY - Random alphanumeric string for cryptographic signing +# http://www.miniwebtool.com/django-secret-key-generator/ +# DEBUG - True to display stack traces when errors are encountered. +# Set this False once auth is installed and working for security. +# ALLOWED_HOSTS - A list of hosts to serve content on. Wildcards accepted. +# Requests for hosts not listed here will be rejected. +# Example: ['example.com', 'auth.example.com'] +# DATABASES - Auth database connection information. +################################ +SECRET_KEY = '' +DEBUG = True +ALLOWED_HOSTS = [] +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'alliance_auth', + 'USER': 'allianceserver', + 'PASSWORD': '', + 'HOST': '127.0.0.1', + 'PORT': '3306', + }, +} + +#################################### # SITE_NAME - Name of the auth site. -#################### +#################################### SITE_NAME = 'Alliance Auth' ################# # EMAIL SETTINGS ################# # DEFAULT_FROM_EMAIL - no-reply email address -# DOMAIN - The Alliance Auth domain (or subdomain) address, starting with http:// +# DOMAIN - The Alliance Auth domain (or subdomain) address, no leading http:// # EMAIL_HOST - SMTP Server URL # EMAIL_PORT - SMTP Server PORT # EMAIL_HOST_USER - Email Username (for gmail, the entire address) @@ -234,7 +240,7 @@ SITE_NAME = 'Alliance Auth' # EMAIL_USE_TLS - Set to use TLS encryption ################# DEFAULT_FROM_EMAIL = 'no-reply@example.com' -DOMAIN = 'https://example.com' +DOMAIN = 'example.com' EMAIL_HOST = 'smtp.gmail.com' EMAIL_PORT = 587 EMAIL_HOST_USER = '' @@ -255,13 +261,13 @@ ESI_SSO_CALLBACK_URL = '' ################# # Login Settings ################# +# LOGIN_REDIRECT_URL - default destination when logging in if no redirect specified +# LOGOUT_REDIRECT_URL - destination after logging out # Both of these redirects accept values as per the django redirect shortcut # https://docs.djangoproject.com/en/1.10/topics/http/shortcuts/#redirect # - url names eg 'authentication:dashboard' # - relative urls eg '/dashboard' # - absolute urls eg 'http://example.com/dashboard' -# LOGIN_REDIRECT_URL - default destination when logging in if no redirect specified -# LOGOUT_REDIRECT_URL - destination after logging out # LOGIN_TOKEN_SCOPES - scopes required on new tokens when logging in. Cannot be blank. # ACCOUNT_ACTIVATION_DAYS - number of days email verification tokens are valid for ################## @@ -270,6 +276,12 @@ LOGOUT_REDIRECT_URL = 'authentication:dashboard' LOGIN_TOKEN_SCOPES = ['esi-characters.read_opportunities.v1'] ACCOUNT_ACTIVATION_DAYS = 1 +##################################################### +## +## Service Configuration +## +##################################################### + ##################### # Alliance Market ##################### @@ -402,10 +414,8 @@ DISCOURSE_SSO_SECRET = '' # IPS4 Configuration ##################################### # IPS4_URL - Base URL of the IPS4 install (no trailing slash). Include http:// -# IPS4_API_KEY - API key provided by IPS4 ##################################### IPS4_URL = '' -IPS4_API_KEY = '' IPS4_DB = { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'alliance_ips4', @@ -452,9 +462,11 @@ FLEETUP_USER_ID = '' FLEETUP_API_ID = '' FLEETUP_GROUP_ID = '' -##################################### -# Logging Configuration -##################################### +##################################################### +## +## Logging Configuration +## +##################################################### # Set log_file and console level to desired state: # DEBUG - basically stack trace, explains every step # INFO - model creation, deletion, updates, etc @@ -559,12 +571,22 @@ LOGGING = { 'level': 'DEBUG', }, 'django': { - 'handlers': ['log_file', 'console', 'notifications'], + 'handlers': ['log_file', 'console'], 'level': 'ERROR', }, } } +##################################################### +## +## Magic Block +## +##################################################### +# This block automagically inserts needed settings if +# certain services are installed. +# Don't touch. +######################################### + # Conditionally add databases only if configured if 'services.modules.phpbb3' in INSTALLED_APPS: DATABASES['phpbb3'] = PHPBB3_DB diff --git a/authentication/admin.py b/authentication/admin.py index de0acd74..f493ced7 100644 --- a/authentication/admin.py +++ b/authentication/admin.py @@ -5,9 +5,12 @@ from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.models import User from django.utils.text import slugify from django import forms +from django.db.models.signals import post_save from authentication.models import State, get_guest_state, CharacterOwnership, UserProfile +from authentication.signals import reassess_on_profile_save from alliance_auth.hooks import get_hooks from services.hooks import ServicesHook +from services.tasks import validate_services def make_service_hooks_update_groups_action(service): @@ -92,6 +95,18 @@ class StateForm(forms.ModelForm): class StateAdmin(admin.ModelAdmin): form = StateForm + fieldsets = ( + (None, { + 'fields': ('name', 'permissions', 'priority'), + }), + ('Membership', { + 'classes': ('collapse',), + 'fields': ('public', 'member_characters', 'member_corporations', 'member_alliances'), + }) + ) + + filter_horizontal = ['member_characters', 'member_corporations', 'member_alliances', 'permissions'] + @staticmethod def has_delete_permission(request, obj=None): if obj == get_guest_state(): @@ -99,4 +114,18 @@ class StateAdmin(admin.ModelAdmin): admin.site.register(CharacterOwnership) -admin.site.register(UserProfile) + + +class UserProfileAdminForm(forms.ModelForm): + def save(self, *args, **kwargs): + # prevent state reassessment to allow manually overriding states + post_save.disconnect(reassess_on_profile_save, sender=UserProfile) + model = super(UserProfileAdminForm, self).save(*args, **kwargs) + post_save.connect(reassess_on_profile_save, sender=UserProfile) + validate_services(model.user) + return model + + +@admin.register(UserProfile) +class UserProfileAdmin(admin.ModelAdmin): + form = UserProfileAdminForm diff --git a/authentication/migrations/0015_user_profiles.py b/authentication/migrations/0015_user_profiles.py index 8a858749..1ad439d0 100644 --- a/authentication/migrations/0015_user_profiles.py +++ b/authentication/migrations/0015_user_profiles.py @@ -149,10 +149,12 @@ def recreate_authservicesinfo(apps, schema_editor): # repopulate main characters for profile in UserProfile.objects.exclude(main_character__isnull=True).select_related('user', 'main_character'): - AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'main_char_id': profile.main_character.character_id}) + AuthServicesInfo.objects.update_or_create(user=profile.user, + defaults={'main_char_id': profile.main_character.character_id}) # repopulate states we understand - for profile in UserProfile.objects.exclude(state__name='Guest').filter(state__name__in=['Member', 'Blue']).select_related('user', 'state'): + for profile in UserProfile.objects.exclude(state__name='Guest').filter( + state__name__in=['Member', 'Blue']).select_related('user', 'state'): AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'state': profile.state.name}) @@ -196,11 +198,15 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=20, unique=True)), - ('priority', models.IntegerField(unique=True)), - ('public', models.BooleanField(default=False)), - ('member_alliances', models.ManyToManyField(blank=True,to='eveonline.EveAllianceInfo')), - ('member_characters', models.ManyToManyField(blank=True,to='eveonline.EveCharacter')), - ('member_corporations', models.ManyToManyField(blank=True,to='eveonline.EveCorporationInfo')), + ('priority', models.IntegerField(unique=True, + help_text="Users get assigned the state with the highest priority available to them.")), + ('public', models.BooleanField(default=False, help_text="Make this state available to any character.")), + ('member_alliances', models.ManyToManyField(blank=True, to='eveonline.EveAllianceInfo', + help_text="Alliances to whose members this state is available.")), + ('member_characters', models.ManyToManyField(blank=True, to='eveonline.EveCharacter', + help_text="Characters to which this state is available.")), + ('member_corporations', models.ManyToManyField(blank=True, to='eveonline.EveCorporationInfo', + help_text="Corporations to whose members this state is available.")), ('permissions', models.ManyToManyField(blank=True, to='auth.Permission')), ], options={ @@ -215,7 +221,8 @@ class Migration(migrations.Migration): models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='eveonline.EveCharacter')), ('state', models.ForeignKey(on_delete=models.SET(authentication.models.get_guest_state), - to='authentication.State', default=authentication.models.get_guest_state_pk)), + to='authentication.State', + default=authentication.models.get_guest_state_pk)), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)), ], diff --git a/authentication/models.py b/authentication/models.py index fa98c1ac..80b4b9d6 100755 --- a/authentication/models.py +++ b/authentication/models.py @@ -4,6 +4,8 @@ from django.db import models from django.contrib.auth.models import User, Permission from authentication.managers import CharacterOwnershipManager, StateManager from eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo +from notifications import notify +from django.utils.translation import ugettext_lazy as _ import logging logger = logging.getLogger(__name__) @@ -13,12 +15,16 @@ logger = logging.getLogger(__name__) class State(models.Model): name = models.CharField(max_length=20, unique=True) permissions = models.ManyToManyField(Permission, blank=True) - priority = models.IntegerField(unique=True) + 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) - member_corporations = models.ManyToManyField(EveCorporationInfo, blank=True) - member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True) - public = models.BooleanField(default=False) + member_characters = models.ManyToManyField(EveCharacter, blank=True, + 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.") + member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True, + help_text="Alliances to whose members this state is available.") + public = models.BooleanField(default=False, help_text="Make this state available to any character.") objects = StateManager() @@ -62,6 +68,9 @@ class UserProfile(models.Model): if commit: logger.info('Updating {} state to {}'.format(self.user, self.state)) self.save(update_fields=['state']) + notify(self.user, _('State Changed'), + _('Your user state has been changed to %(state)s') % ({'state': state}), + 'info') def __str__(self): return str(self.user) diff --git a/authentication/signals.py b/authentication/signals.py index e4ffe373..399b92e7 100644 --- a/authentication/signals.py +++ b/authentication/signals.py @@ -108,3 +108,10 @@ def assign_state_on_reactivate(sender, instance, *args, **kwargs): # If we're saving a user and that user is in the Guest state, assume is_active was just set to True and assign state if instance.is_active and instance.profile.state == get_guest_state(): instance.profile.assign_state() + + +@receiver(post_save, sender=EveCharacter) +def check_state_on_character_update(sender, instance, *args, **kwargs): + # if this is a main character updating, check that user's state + if instance.userprofile: + instance.userprofile.assign_state() diff --git a/authentication/templates/registered/base.html b/authentication/templates/registered/base.html index fc351abd..b67e9f17 100644 --- a/authentication/templates/registered/base.html +++ b/authentication/templates/registered/base.html @@ -80,10 +80,6 @@