import re from django.db.models import Q from django.shortcuts import redirect, render from django.views.generic.list import ListView import troggle.settings as settings from troggle.core.models.logbooks import QM, LogbookEntry, PersonLogEntry, writelogbook from troggle.core.models.survex import SurvexBlock, SurvexFile from troggle.core.models.troggle import Expedition, Person from troggle.core.models.wallets import Wallet from troggle.core.utils import TROG, current_expo from troggle.parsers.imports import import_logbook """These views are for logbook items when they appear in an 'expedition' page and for persons: their individual pages and their perseonexpedition pages. It uses the global object TROG to hold some cached pages. """ todo = """- Fix the get_person_chronology() display bug. - Fix id= value preservation on editing """ def notablepersons(request): def notabilitykey(person): return person.notability() persons = Person.objects.all() # From what I can tell, "persons" seems to be the table rows, while "pcols" is the table columns. - AC 16 Feb 09 pcols = [] ncols = 4 nc = int((len(persons) + ncols - 1) / ncols) for i in range(ncols): pcols.append(persons[i * nc : (i + 1) * nc]) notablepersons = [] # Needed recoding because of Django CVE-2021-45116 for person in persons: if person.bisnotable(): notablepersons.append(person) notablepersons.sort(key=notabilitykey, reverse=True) return render( request, "notablepersons.html", {"persons": persons, "pcols": pcols, "notablepersons": notablepersons} ) def expedition(request, expeditionname): """Returns a rendered page for one expedition, specified by the year e.g. '2019'. If page caching is enabled, it caches the dictionaries used to render the template page. This is not as difficult to understand as it looks. Yes there are many levels of indirection, with multiple trees being traversed at the same time. And the Django special syntax makes this hard for normal Python programmers. Remember that 'personexpedition__expedition' is interpreted by Django to mean the 'expedition' object which is connected by a foreign key to the 'personexpedition' object, which is a field of the PersonLogEntry object: PersonLogEntry.objects.filter(personexpedition__expedition=expo) Queries are not evaluated to hit the database until a result is actually used. Django does lazy evaluation. """ current = current_expo() # creates new expo after 31st Dec. try: expo = Expedition.objects.get(year=int(expeditionname)) except: message = ( "Expedition not found - database apparently empty, you probably need to do a full re-import of all data." ) return render(request, "errors/generic.html", {"message": message}) ts = TROG["pagecache"]["expedition"] # not much use unless single user! if request.user.is_authenticated: logged_in = True if "reload" in request.GET: if expeditionname in ts: del ts[expeditionname] # clean out cache for page expo.logbookentry_set.all().delete() import_logbook(year=expo.year) else: logged_in = False if settings.CACHEDPAGES: if expeditionname in ts: # print('! - expo {expeditionanme} using cached page') return render(request, "expedition.html", {**ts[expeditionname], "logged_in": logged_in}) expeditions = Expedition.objects.all() # top menu only, evaluated only when template renders entries = expo.logbookentry_set.all() blocks = expo.survexblock_set.all() dateditems = list(entries) + list(blocks) # evaluates the Django query and hits db dates = sorted(set([item.date for item in dateditems])) allpersonlogentries = PersonLogEntry.objects.filter(personexpedition__expedition=expo) personexpodays = [] for personexpedition in expo.personexpedition_set.all(): expotrips = allpersonlogentries.filter(personexpedition=personexpedition) # lazy expoblocks = blocks.filter(survexpersonrole__personexpedition=personexpedition) prow = [] for date in dates: personentries = expotrips.filter(logbook_entry__date=date) # lazy personblocks = set(expoblocks.filter(date=date)) # not lazy pcell = {} pcell["personentries"] = personentries pcell["survexblocks"] = personblocks if issunday := (date.weekday() == 6): # WALRUS pcell["sunday"] = issunday prow.append(pcell) personexpodays.append({"personexpedition": personexpedition, "personrow": prow, "sortname": personexpedition.person.last_name}) ts[expeditionname] = { "year": int(expeditionname), "expedition": expo, "expeditions": expeditions, "personexpodays": personexpodays, "settings": settings, "dateditems": dateditems, "dates": dates, } TROG["pagecache"]["expedition"][expeditionname] = ts[expeditionname] return render(request, "expedition.html", {**ts[expeditionname], "logged_in": logged_in}) class Expeditions_tsvListView(ListView): """This uses the Django built-in shortcut mechanism It defaults to use a template with name /_list.html. https://www.agiliq.com/blog/2017/12/when-and-how-use-django-listview/ https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Generic_views Either a queryset variable or set_queryset() function is used, but not needed if you want all the obejcts of a particaulr type in which case just set model = """ template_name = "core/expeditions_tsv_list.html" # if not present then uses core/expedition_list.html # queryset = Expedition.objects.all() # context_object_name = 'expedition' model = Expedition # equivalent to .objects.all() for a queryset class Expeditions_jsonListView(ListView): template_name = "core/expeditions_json_list.html" model = Expedition class QMs_jsonListView(ListView): template_name = "core/QMs_json_list.html" model = QM def person(request, slug=""): """Now very much simpler with an unambiguous slug """ try: this_person = Person.objects.get(slug=slug) except: msg = f" Person '{slug=}' not found in database. DATABASE RESET required - ask a nerd." print(msg) return render(request, "errors/generic.html", {"message": msg}) current_year = current_expo() return render(request, "person.html", {"person": this_person, "year": current_year}) def get_person_chronology(personexpedition): """ This is just a nasty convoluted way of trying the make the template do more work than it is sensible to ask it to do. Rewrite more simply with the login in the python, not in Django template language (you bastard Curtis). """ res = {} for personlogentry in personexpedition.personlogentry_set.all(): a = res.setdefault(personlogentry.logbook_entry.date, {}) a.setdefault("personlogentries", []).append(personlogentry) for personrole in personexpedition.survexpersonrole_set.all(): if personrole.survexblock.date: # avoid bad data from another bug a = res.setdefault(personrole.survexblock.date, {}) a.setdefault("personroles", []).append(personrole.survexblock) # build up the tables rdates = sorted(list(res.keys())) res2 = [] for rdate in rdates: personlogentries = res[rdate].get("personlogentries", []) personroles = res[rdate].get("personroles", []) for n in range(max(len(personlogentries), len(personroles))): res2.append( ( (n == 0 and rdate or "--"), (n < len(personlogentries) and personlogentries[n]), (n < len(personroles) and personroles[n]), ) ) return res2 def personexpedition(request, slug="", year=""): try: person = Person.objects.get(slug=slug) this_expedition = Expedition.objects.get(year=year) personexpedition = person.personexpedition_set.get(expedition=this_expedition) personchronology = get_person_chronology(personexpedition) current_year = current_expo() return render( request, "personexpedition.html", {"personexpedition": personexpedition, "personchronology": personchronology, "year": current_year} ) except: msg = f" Person '{slug=}' or year '{year=}' not found in database. Please report this to a nerd." print(msg) return render(request, "errors/generic.html", {"message": msg}) def logentrydelete(request, year): """This only gets called by a POST from the logreport page This function is dedicated to James Waite who managed to make so many duplicate logbook entries that we needed a sopecial mechanism to delete them. """ for i in request.POST: print(f" - '{i}' {request.POST[i]}") eslug = request.POST["entry_slug"] entry = LogbookEntry.objects.get(slug=eslug) # OK we delete it from the db and then re-save logbook.html file # to ensure that the permanent record also has the entry deleted. entry.delete() print(f"- Rewriting the entire {year} logbook to disc ") filename= "logbook.html" try: writelogbook(year, filename) # uses a template except: message = f'! - Logbook saving failed - \n!! Permissions failure ?! on attempting to save file "logbook.html"' print(message) return render(request, "errors/generic.html", {"message": message}) return redirect(f"/logreport/{year}") def logreport(request, year=1999): """ Remember that 'personexpedition__expedition' is interpreted by Django to mean the 'expedition' object which is connected by a foreign key to the 'personexpedition' object, which is a field of the PersonLogEntry object: PersonLogEntry.objects.filter(personexpedition__expedition=expo) """ # print(f"logreport(): begun") expeditions = Expedition.objects.all() # top menu only, evaluated only when template renders dates = None dateditems = None logged_in = False if request.user.is_superuser: # expoadmin is both .is_staff and ._is_superuser logged_in = True try: expo = Expedition.objects.get(year=int(year)) except: message = ( "Expedition not found - database apparently empty, you probably need to do a full re-import of all data." ) return render(request, "errors/generic.html", {"message": message}) entries = expo.logbookentry_set.all() # lazy list dateditems = list(entries) # evaluates the Django query and hits db dates = sorted(set([item.date for item in dateditems])) try: for entry in dateditems: people = PersonLogEntry.objects.filter(logbook_entry=entry) entry.who = [] for p in people: if p.is_logbook_entry_author: entry.author = p else: entry.who.append(p) # print(f"logreport(): trying..") context = { "year": year, "expedition": expo, "expeditions": expeditions, "settings": settings, "dateditems": dateditems, "dates": dates, "logged_in": logged_in, } # print(f"logreport(): rendering..") return render(request, "logreport.html", context) except Exception as e: msg = f' Logbook report for year:"{year}" not implemented yet\n{e}\n {context}' print(msg) return render(request, "errors/generic.html", {"message": msg}) def logbookentry(request, date, slug): # start = time.time() trips = LogbookEntry.objects.filter(date=date) # all the trips not just this one this_logbookentry = trips.filter(date=date, slug=slug) year = slug[:4] if this_logbookentry: if len(this_logbookentry) > 1: # BUG return render(request, "object_list.html", {"object_list": this_logbookentry}) else: # https://stackoverflow.com/questions/739776/how-do-i-do-an-or-filter-in-a-django-query wallets = Wallet.objects.filter(Q(survexblock__date=date) | Q(walletdate=date)).distinct() svxothers = SurvexFile.objects.filter(survexblock__date=date).distinct() this_logbookentry = this_logbookentry[0] # This is the only page that uses next_.. and prev_.. # and it is calculated on the fly in the model return render( request, "logbookentry.html", {"logbookentry": this_logbookentry,"trips": trips, "svxothers": svxothers, "wallets": wallets, "year": year}, ) else: msg = f' Logbook entry slug:"{slug}" not found in database on date:"{date}" ' print(msg) return render(request, "errors/generic.html", {"message": msg}) def get_people(request, expeditionslug): exp = Expedition.objects.get(year=expeditionslug) return render(request, "options.html", {"items": [(pe.slug, pe.name) for pe in exp.personexpedition_set.all()]}) def get_logbook_entries(request, expeditionslug): exp = Expedition.objects.get(year=expeditionslug) return render( request, "options.html", {"items": [(le.slug, f"{le.date} - {le.title}") for le in exp.logbookentry_set.all()]} )