2
0
mirror of https://expo.survex.com/repositories/troggle/.git synced 2026-02-11 06:56:49 +00:00

Use logon not cookie when editing pages

This commit is contained in:
2025-01-26 19:04:56 +00:00
parent 7fab42fa9e
commit ce508b0eb2
8 changed files with 67 additions and 206 deletions

View File

@@ -1,160 +0,0 @@
import os
import sys
import urllib.parse
from pathlib import Path
"""Settings for a troggle installation which may vary among different
installations: for development or deployment, in a docker image or
python virtual environment (venv), on ubuntu, debian or in Windows
System for Linux (WSL), on the main server or in the potato hut,
using SQLite or mariaDB.
It sets the directory locations for the major parts of the system so
that e.g. expofiles can be on a different filesystem.
This file is included at the end of the main troggle/settings.py file so that
it overwrites defaults in that file.
NOTE this file is vastly out of sync with troggle/_deploy/wsl/localsettings.py
which is the most recent version used in active maintenance. There should be
essential differences, but there and many, many non-essential differences which
should be eliminated for clarity and to use modern idioms. 8 March 2023.
"""
print(" * importing troggle/localsettings.py")
# DO NOT check this file into the git repo - it contains real passwords.
EXPOFILESREMOTE = False # if True, then re-routes urls in expofiles to remote sever
#SECURE_SSL_REDIRECT = True # breaks 7 tests in test suite 301 not 200 (or 302) and runserver fails completely
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'NAME' : 'troggle', # Or path to database file if using sqlite3.
'USER' : 'expo', # Not used with sqlite3.
'PASSWORD' : 'uFqP56B4XleeyIW', # Not used with sqlite3.
'HOST' : '', # Set to empty string for localhost. Not used with sqlite3.
'PORT' : '', # Set to empty string for default. Not used with sqlite3.
}
}
EXPOUSER = 'expo'
EXPOUSERPASS = '161:gosser'
EXPOADMINUSER = 'expoadmin'
EXPOADMINUSERPASS = 'gosser:161'
EXPOUSER_EMAIL = 'wookey@wookware.org'
EXPOADMINUSER_EMAIL = 'wookey@wookware.org'
REPOS_ROOT_PATH = '/home/expo/'
sys.path.append(REPOS_ROOT_PATH)
sys.path.append(REPOS_ROOT_PATH + 'troggle')
# Define the path to the django app (troggle in this case)
PYTHON_PATH = REPOS_ROOT_PATH + 'troggle/'
PHOTOS_YEAR = "2023"
# add in 358 when they don't make it crash horribly
NOTABLECAVESHREFS = [ "290", "291", "359", "264", "258", "204", "76", "107"]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
PYTHON_PATH + "templates"
],
'OPTIONS': {
'debug': 'DEBUG',
'context_processors': [
# django.template.context_processors.csrf, # is always enabled and cannot be removed, sets csrf_token
'django.contrib.auth.context_processors.auth', # knowledge of logged-on user & permissions
'core.context.troggle_context', # in core/troggle.py
'django.template.context_processors.debug',
#'django.template.context_processors.request', # copy of current request, added in trying to make csrf work
'django.template.context_processors.i18n',
'django.template.context_processors.media', # includes a variable MEDIA_URL
'django.template.context_processors.static', # includes a variable STATIC_URL
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
],
'loaders': [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader', #For each app, inc admin, in INSTALLED_APPS, loader looks for /templates
# insert your own TEMPLATE_LOADERS here
]
},
},
]
PUBLIC_SITE = True
# This should be False for normal running
DEBUG = True
CACHEDPAGES = True # experimental page cache for a handful of page types
# executables:
CAVERN = 'cavern' # for parsing .svx files and producing .2d files
SURVEXPORT = 'survexport' # for parsing .3d files and producing .pos files
PV = "python" + str(sys.version_info.major) + "." + str(sys.version_info.minor)
LIBDIR = Path(REPOS_ROOT_PATH) / 'lib' / PV
EXPOWEB = Path(REPOS_ROOT_PATH + 'expoweb/')
SURVEYS = REPOS_ROOT_PATH
SURVEY_SCANS = REPOS_ROOT_PATH + 'expofiles/surveyscans/'
FILES = REPOS_ROOT_PATH + 'expofiles'
PHOTOS_ROOT = REPOS_ROOT_PATH + 'expofiles/photos/'
TROGGLE_PATH = Path(__file__).parent
TEMPLATE_PATH = TROGGLE_PATH / 'templates'
MEDIA_ROOT = TROGGLE_PATH / 'media'
JSLIB_ROOT = TROGGLE_PATH / 'media' / 'jslib' # used for CaveViewer JS utility
CAVEDESCRIPTIONS = EXPOWEB / "cave_data"
ENTRANCEDESCRIPTIONS = EXPOWEB / "entrance_data"
PYTHON_PATH = REPOS_ROOT_PATH + 'troggle/'
#URL_ROOT = 'http://expo.survex.com/'
URL_ROOT = '/'
DIR_ROOT = Path("") #this should end in / if a value is given
EXPOWEB_URL = '/'
SURVEYS_URL = '/survey_scans/'
REPOS_ROOT_PATH = Path(REPOS_ROOT_PATH)
SURVEX_DATA = REPOS_ROOT_PATH / "loser"
DRAWINGS_DATA = REPOS_ROOT_PATH / "drawings"
EXPOFILES = REPOS_ROOT_PATH / "expofiles"
SCANS_ROOT = EXPOFILES / "surveyscans"
PHOTOS_ROOT = EXPOFILES / "photos"
#EXPOFILES = urllib.parse.urljoin(REPOS_ROOT_PATH, 'expofiles/')
PHOTOS_URL = urllib.parse.urljoin(URL_ROOT, '/photos/')
# MEDIA_URL is used by urls.py in a regex. See urls.py & core/views_surveys.py
MEDIA_URL = '/site_media/'
STATIC_URL = urllib.parse.urljoin(URL_ROOT , '/static/') # used by Django admin pages. Do not delete.
JSLIB_URL = urllib.parse.urljoin(URL_ROOT , '/javascript/') # always fails, try to revive it ?
# STATIC_ROOT removed after merging content into MEDIA_ROOT. See urls.py & core/views/surveys.py
#TINY_MCE_MEDIA_ROOT = STATIC_ROOT + '/tiny_mce/' # not needed while TinyMCE not installed
#TINY_MCE_MEDIA_URL = STATIC_URL + '/tiny_mce/' # not needed while TinyMCE not installed
LOGFILE = '/var/log/troggle/troggle.log'
IMPORTLOGFILE = '/var/log/troggle/import.log'
# Sanitise these to be strings as Django seems to be particularly sensitive to crashing if they aren't
STATIC_URL = str(STATIC_URL) + "/"
MEDIA_URL = str(MEDIA_URL) + "/"
print(" + finished importing troggle/localsettings.py")

View File

@@ -78,7 +78,12 @@ class Expedition(TroggleModel):
return reverse("expedition", args=[self.year])
class Person(TroggleModel):
"""single Person, can go on many years"""
"""single Person, can go on expo many years
Note that the class "User" and the class "Group
are standrd Django classes
definied in django.contrib.auth.models
"""
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
@@ -100,8 +105,7 @@ class Person(TroggleModel):
def get_absolute_url(self):
# we do not use URL_ROOT any more.
return reverse("person", kwargs={"slug": self.slug})
return reverse("person", kwargs={"first_name": self.first_name, "last_name": self.last_name})
class Meta:
verbose_name_plural = "People"
ordering = ("orderref",) # "Wookey" makes too complex for: ('last_name', 'first_name')

View File

@@ -157,6 +157,25 @@ def current_expo():
else:
return settings.EPOCH.year # this is 1970
def is_identified_user(user):
if user.is_anonymous:
return False
if user.username in ["expo", "expoadmin"]:
return False
return True
def get_git_string(user):
if not is_identified_user(user):
return None
else:
people = Person.objects.filter(user=user)
if len(people) != 1:
# someone like "fluffy-bunny" not associated with a Person
return None
person = people[0]
return f"{person.fullname} <{user.email}>"
def parse_aliases(aliasfile):
"""Reads a long text string containing pairs of strings:
(alias, target)

View File

@@ -19,7 +19,9 @@ from troggle.core.utils import (
current_expo,
get_cookie,
git_string,
get_git_string,
write_and_commit,
is_identified_user
)
from troggle.core.views.editor_helpers import HTMLarea
from troggle.core.views.uploads import edittxtpage
@@ -455,15 +457,17 @@ def editexpopage(request, path):
print("### File not found ### ", filepath)
filefound = False
editor = get_cookie(request)
current_user = request.user
if identified_login := is_identified_user(current_user):
editor = get_git_string(current_user)
else:
editor = get_cookie(request)
if request.method == "POST": # If the form has been submitted...
pageform = ExpoPageForm(request.POST) # A form bound to the POST data
if pageform.is_valid(): # Form valid therefore write file
editor = pageform.cleaned_data["who_are_you"]
editor = git_string(editor)
# print("### \n", str(pageform)[0:300])
# print("### \n csrfmiddlewaretoken: ",request.POST['csrfmiddlewaretoken'])
if filefound:
headmatch = re.match(r"(.*)<title>.*</title>(.*)", head, re.DOTALL + re.IGNORECASE)
if headmatch:
@@ -490,7 +494,7 @@ def editexpopage(request, path):
if not filefound or result != html: # Check if content changed at all
edit_response = HttpResponseRedirect(reverse("expopage", args=[path])) # Redirect after POST
edit_response.set_cookie('editor_id', editor, max_age=COOKIE_MAX_AGE) # cookie expires after COOKIE_MAX_AGE seconds
print(f"Cookie set: {editor} for {COOKIE_MAX_AGE/3600} hours")
print(f"Cookie set: {editor} for {COOKIE_MAX_AGE/(24*3600)} days")
try:
change_message = pageform.cleaned_data["change_message"]
editor = pageform.cleaned_data["who_are_you"]
@@ -507,9 +511,9 @@ def editexpopage(request, path):
(title,) = m.groups()
else:
title = ""
pageform = ExpoPageForm(initial={"who_are_you":editor, "html": body, "title": title})
pageform = ExpoPageForm(initial={"identified_login": identified_login, "who_are_you":editor, "html": body, "title": title})
else:
pageform = ExpoPageForm(initial={"who_are_you":editor})
pageform = ExpoPageForm(initial={"identified_login": identified_login, "who_are_you":editor})
return render(
@@ -540,6 +544,8 @@ class ExpoPageForm(forms.Form):
"style": "vertical-align: text-top;"}
)
)
identified_login = forms.BooleanField(widget=forms.CheckboxInput(attrs={"onclick":"return false"})) # make it readonly
who_are_you = forms.CharField(
widget=forms.Textarea(
attrs={"cols": 90, "rows": 1, "placeholder": "You have edited this page, who are you ? e.g. 'Animal <mta@gasthof.expo>'",

View File

@@ -163,7 +163,7 @@ def controlpanel(request):
return render(
request,
"controlPanel.html",
{"error": ' - Needs "expoadmin" logon. \nLogout and login again.',
{"error": ' - Needs "expoadmin" or superuser logon. \nLogout and login again.',
"year": current_expo()}
)

View File

@@ -8,20 +8,18 @@ from django.shortcuts import redirect, render
from django.urls import reverse
import troggle.settings as settings
from troggle.core.utils import (
COOKIE_MAX_AGE,
WriteAndCommitError,
current_expo,
get_cookie,
git_string,
write_and_commit,
)
from troggle.core.models.troggle import DataIssue, Person
from troggle.core.views.editor_helpers import HTMLarea
from troggle.core.utils import (
COOKIE_MAX_AGE,
WriteAndCommitError,
add_commit,
current_expo,
get_cookie,
get_git_string,
git_string,
is_identified_user,
write_and_commit,
current_expo
)
from troggle.parsers.users import get_encryptor, ENCRYPTED_DIR, how_many_previous_expos
@@ -51,27 +49,17 @@ def signupok(request):
{"year": SIGNUP_YEAR, "dates": SIGNUP_DATES, "signup_user": signup_user, "signedup_people": signedup_people},
)
def signup(request):
"""Display and processes the applicant signup form for the forthcoming expo
The user must be logged-on as a personal login and that is
who is being sighned up. You can't signup someone else.
who is being signed up. You can't signup someone else.
"""
signup_user = request.user
if signup_user.is_anonymous:
personal_login = False
elif signup_user.username in ["expo", "expoadmin"]:
personal_login = False
else:
personal_login = True
if personal_login:
people = Person.objects.filter(user=signup_user)
if len(people) != 1:
# someone like "fluffy-bunny" not associated with a Person
return HttpResponseRedirect("/accounts/login/?next=/signup")
signup_person = people[0]
editor = f"{signup_person.fullname} <{signup_user.email}>"
identified_login = is_identified_user(signup_user)
if identified_login:
editor = get_git_string(signup_user)
else:
editor = f"troggle <signup_anon@austria.expo>"
@@ -89,7 +77,7 @@ def signup(request):
print(f" # Signup form INVALID\n{pageform.errors} ")
return render(
request, "login/signup.html",
{"form": pageform, "personal_login": personal_login,
{"form": pageform, "identified_login": identified_login,
"year": SIGNUP_YEAR, "dates": SIGNUP_DATES,
}
)
@@ -107,16 +95,15 @@ def signup(request):
"top_tent_cap": 2,
"base_tent_cap": 3,
}
if personal_login:
if identified_login:
initial_context["name"] = signup_person.fullname
initial_context["email"] = signup_user.email
initial_context["experience"] = experience
pageform = ExpoSignupForm(initial=initial_context)
return render(
request, "login/signup.html",
{"form": pageform, "personal_login": personal_login,
{"form": pageform, "identified_login": identified_login,
"year": SIGNUP_YEAR, "dates": SIGNUP_DATES,
},
)

View File

@@ -22,11 +22,11 @@ Passwords are only ever stored as hashes using the standard Django functions.
todo = """
- Not fully tested, needs experience
- Need to write to BierBook for signups
- Need to check/register with lists.wookware.org for email
"""
SUPER_USERS = ["philip-sargent"] # list of userids who get the same rights as "expoadmin" i.e. the Django control panel
USERS_FILE = "users.json"
ENCRYPTED_DIR = "encrypted"
@@ -54,8 +54,13 @@ def register_user(u, email, password=None, pwhash=None, fullname=""):
# user.set_password(None) # use Django special setting for invalid password, but then FAILS to send password reset email
user.set_password("secret") # Why is the Django logic broken. Hmph.
print(f" # setting INVALID password for user {u}, must be reset by password_reset")
user.is_staff = False
user.is_superuser = False
if u in SUPER_USERS:
user.is_staff = True
user.is_superuser = True
print(f"** {u} is SUPER and can access everything on the Django control panel")
else:
user.is_staff = False
user.is_superuser = False
user.save()
print(f" - receated and reset user '{user}'")
except Exception as e:

View File

@@ -45,7 +45,7 @@ Loser Expo {{year}} SIGN-UP Form
<p>Submitting this form will subscribe you to the expo mailing list
if you are not already subscribed.
</p>
{% if personal_login %}
{% if identified_login %}
{% else %}
<button class="fancybutton" style="padding: 0.5em 25px; font-size: 100%;" onclick="window.location.href='/accounts/register/'" value = "Go to">
You need to register a personal login before you can signup to attend &rarr;
@@ -166,7 +166,7 @@ Loser Expo {{year}} SIGN-UP Form
<h2>All done?</h2>
<!-- <p>Click the <b>Preview</b> button below to review your submission.</p> -->
<div style="text-align: center">
{% if personal_login %}
{% if identified_login %}
<button class="fancybutton" style="padding: 0.5em 25px; font-size: 100%;" type = "submit" >
Submit &rarr;
</button>