From 6394f333e8ba5bc87b9b7807386c2eb4b47bb6dd Mon Sep 17 00:00:00 2001 From: Expo on server Date: Mon, 5 Feb 2024 21:59:51 +0000 Subject: [PATCH] Logbook edited 2023-11-15a --- years/2023/logbook.html | 1029 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 990 insertions(+), 39 deletions(-) diff --git a/years/2023/logbook.html b/years/2023/logbook.html index c90e4ecf4..98915e356 100644 --- a/years/2023/logbook.html +++ b/years/2023/logbook.html @@ -14,7 +14,7 @@ maintain half a dozen parser functions. Sorry about all the crap that surrounds the image tags which has been imported along with the content when UK Caving blogs have been parsed. -Exported on 2024-02-05 20:02 using either the control panel webpage or when editing a logbook entry online +Exported on 2024-02-05 21:02 using either the control panel webpage or when editing a logbook entry online See troggle/code/views/other.py and core.models/logbooks.py writelogbook(year, filename) --> @@ -194,7 +194,7 @@ serious nerding to get WiFi internet working in potato hut.
2023-07-03
-
Mike Butcher, Honorata Bogusz, Jana Podbelsek, Radost Waszkiewicz, Will Kay,
+
Radost Waszkiewicz, Mike Butcher, Jana Podbelsek, Will Kay, Honorata Bogusz,
plateau - second water collection tarp


Me, Radost, Mike, Jana and Will went to top camp to set up main tarp and the second water collection tarp (which was a success). @@ -204,7 +204,7 @@ main tarp and the second water collection tarp (which was a success).
2023-07-03
-
Christian Kuhlmann, Harry Kettle, Janis Huns, Charlotte Payne, Emma Caspers,
+
Charlotte Payne, Christian Kuhlmann, Janis Huns, Emma Caspers, Harry Kettle,
Balkon - Rescuing Balcony ropes


A crack team of expo's hardest cavers was assembled to take a rope out of balcony that was left there last year.

@@ -220,7 +220,7 @@ at about 5pm. Charlotte and Harry were both very grumpy walking back with very h
2023-07-03
-
Amelia Oliver, Emily Mabbett, Jonty Pine,
+
Amelia Oliver, Jonty Pine, Emily Mabbett,
Fishface - Digging the snow out of Fishface entrance


After a carrying day on the 2nd, we suspected ff would be snowed in. A faff morning of acquiring shovel. We got to ff with shovels, discovering it did need digging. Jonty got particularly into this, producing quit the snow trench. @@ -266,7 +266,7 @@ non-working internal wifi on netbook causing config problems even though it was
2023-07-04
-
Wassil Janssen, Ashley Gregg, Philip Balister, Amelia Oliver, Merryn Matthews, Sarah Parker,
+
Amelia Oliver, Sarah Parker, Wassil Janssen, Philip Balister, Merryn Matthews, Ashley Gregg,
topcamp - Carry and set-up


Ash, Merryn, Amelia in [com?] Philip B., Sarah and me @@ -279,7 +279,7 @@ came down the mountain.
2023-07-04
-
Alice Kirby, Harry Kettle, Christian Kuhlmann, Charlotte Payne, Will Kay,
+
Charlotte Payne, Alice Kirby, Christian Kuhlmann, Will Kay, Harry Kettle,
Surface - Rope carry to Homecoming + Trial walk from Garlic Cave to Car Park on painted track


After more faff from Chi, we set off about 10AM to carry gear and check out the route to Homecoming. Fishface to Homecoming was well cairned and fairly straightforward to follow.

Gear ledt at Homecoming entrance: @@ -320,7 +320,7 @@ Nice write up [re Honorata's 3-day update]. The rope there now is stuff we bough
2023-07-05
-
Lizzie Caisley, Sarah Parker, Joel Stobbart, Philip Balister, Maddie Kirby,
+
Philip Balister, Lizzie Caisley, Joel Stobbart, Maddie Kirby, Sarah Parker,
Surface - Reflecting the Homecoming -> Fishface route, scoping out a Homecoming to col route


Joel, Lizzie and Philip fettled the tarp (where some water had pooled overnight) whilst I had a go at attaching the big Daren drum (filled with the solar panels and cables) to my rucksack, ready to carry from top camp to Garlic Cave. @@ -356,7 +356,7 @@ return.
2023-07-06
-
Joel Stobbart, Buck Blake, Jonty Pine,
+
Joel Stobbart, Jonty Pine, Buck Blake,
Fishface - Rigging and pitch-measuring trip


An ambitious day which slowly went increasingly wrong. The group departed base camp at a stunning 8.20AM, blitzed up the plateau and promtly became mired in several hours of faff which soon descended into more festering. The plan had been to rig the entrance series of Fishface down to the bottom of Blitzen Boulevard (4th pitch), from where we expected everything to be left rigged. However, confusion about rope lengths and metalwork and some impressively long grike trips meant that the two shallow pushing groups planning to explore from Blitzen and Liquid Luck caught us up at Top Camp. Uncle Mike was not amused. We swiftly bombed down to FF in 25 mins and started rigging. Joel was left to do everything as Jonty's light 'broke'.

The entrance pitch/spiral traverse thing was completed on a 40m rope. Tasteful noods (2nd pitch) didn't quite go on a 27m due to rerigging around the top rebelay to avoid rub, so Joel initially reached the bottom on a knot pass and Jonty rerigged to the bottom on a 32m. The traverse at the bottom of Tasteful Noods (2A) and pitch 3 both had rope left from last year but not rigged - some of the knots didn't line up and required rerigging but the lengths were fine. Pendulum pitch needs some more bolts at the bottom, possibly as a traverse, to make getting on and off the pitch less deathy. Uncle Mike rigged Blitzen for us as we got too scared, he insisted the step over the huge rift with no traverse was fine, so we left him to it. There may or may not be a bolt there now @@ -368,7 +368,7 @@ return.
2023-07-06
-
Charlotte Payne, Emily Mabbett, Mike Butcher, Lizzie Caisley, Emma Caspers,
+
Lizzie Caisley, Charlotte Payne, Mike Butcher, Emma Caspers, Emily Mabbett,
Fishface - Surveying around Benign Bubble Baby Bypass


After hiking up to plateau to arrive for midday, we discovered the advanced rigging party still above ground... In order to leave them time to start rigging the first 4 pitches of Fishface, an ungodly amount of faff began. We finally arrived at the cave at 3pm, thinking we'd left plenty of time for the riggers, we quickly changed and headed underground. Alas, our hopes were crushed as we came to the bottom of the first pitch to discover the other pushing party sat freezing their tits off at the top of the second pitch as the riggers rigged just below them. Needless to say, it took some time to descent to the bottom of Blitzen Boulevard with Mike taking over the rigging of the 4th pitch after a debate over placing another bolt at the pitch head. Once arrived at Benign Bubble Baby Bypass, we conducted a quick something time to refresh our surveying technique before splitting into 2 groups. Me and Mike went ahead through the tube leading on through the bottom of the climb heading towards the liquid luck ptch head to bolt the small pitch at the end of the traverse whilst the others began surveying from said turn off. As me and Mike arrived at the pitch head, I asked if I could begin my bolting lesson before a big scary hole. I was refused. Instead I was told to tie the rope around a small head sized bolder wedged in the rift, which were currently both stood on. I thought this was a silly idea but obliged. The actual bolting of the pitch went well (I think?), however comments about placing bolts higher were made which was somewhat impossible given my height. The final bolting and rigging became passable so I decended the large (4m...) hole.

As we finished bolting, the survey team emerged behind us. At the bottom of the pitch, 3 leads emerged, a large passage with a traverse sloping down in front of us, a small drafty tube to the right, and a hole heading into the ceiling behind us. With the rigging team and pushing team 2 in the near vicintity of the cave around us, me and Mike were a bit naughty and scooped the large passage in front of us, as we reached the end, we could hear voices in the rift above us, thinking it was the other pushing team we called up. It was not. It was Buck from the rigging team stood near the base of Blitzen Boulevard essentially where we started. Discovering we'd done a large circle , we returned to the base of the pitch where the survey team were. As we arrived, Zac from the team 2 appeared in the hole above us, they had also done an circle. @@ -394,7 +394,7 @@ return.
2023-07-06
-
Amelia Oliver, Emma Caspers, Zac Woodford, Ashley Gregg,
+
Ashley Gregg, Amelia Oliver, Zac Woodford, Emma Caspers,
Fishface - Surveying the top of liquid luck


Gave the rigging team ~3hr headstart but we still caught up with them at the bottom of first pitch. Emma and Zac then sat around while Ash and Mealy calibrated the disto. We were waiting so long that Uncle Mike's surveying group caught up. We waited in a a bothy for them to pass us before slowly, one-by-one, following on. We then caught up with them again at the top of Blitzen pitch and had to bothy again (after Mealy led us on too low in the rift).

At the beginning of BBBB (Benign Babble Baby Bypass) we talked with Mike's group pushing the other lead and descending some small pitches to push ours. We faffed around a while trying to find it, but when we did, we found it just looped back around to Mike's group's lead. We surveyed it anyway. @@ -439,7 +439,7 @@ Bolts used:
2023-07-07
-
Nadia Raeburn, Jonty Pine, Philip Sargent, Janis Huns,
+
Janis Huns, Nadia Raeburn, Philip Sargent, Jonty Pine,
Hiking to Garlic Cave


Jonty's car: Jonty/Nads/Janis up to Garlic and return this evening. Taking new reflectors made this morning.
T/U: 0.0 hours
@@ -448,7 +448,7 @@ Bolts used:
2023-07-07
-
Honorata Bogusz, Mike Butcher, Radost Waszkiewicz,
+
Honorata Bogusz, Radost Waszkiewicz, Mike Butcher,
1623-290 - pushing deep Fishgesicht


We pushed 60m at the top of "Clap My Pitch Up". Pushing required bolting ~20m of a traverse ( Mike adds: 20m ish of the traverse was new passage surveyed this year but there was about 30m of traverse bolted this year which was naughtily pushed by me - Mike - and Luke last year in the Red Light Spells Danger trip so the traverse is about 50ish m long). The final few metres of the traverse go above a sizeable pitch (30m?). We names the bolted traverse, "European Federalists". At the end of the traverse, we continued walking for another ~40m until arriving at the top of a massive pitch - further pushing would require bolting. The distance from where we were standing to the furthest point down the pitch measured with the disto was ~40m. We kicked rocks down the pitch and the sound continued for 10s, giving rise to a presumption that the pitch may be very deep. If it connects to "Clap My Pitch Up", it's at least 100m deep. IMPORTANT: the traverse passage and the walkable continuation are muddy and slippery; posing a hazard of falling down the pitch. Ash, Jonty, Mealy and Janis want to push the lead further on Saturday 8th July.
T/U: 0.0 hours
@@ -457,7 +457,7 @@ Bolts used:
2023-07-07
-
Emily Mabbett, Harry Kettle, Charlotte Payne,
+
Emily Mabbett, Charlotte Payne, Harry Kettle,
Homecoming - Rigging towards Watershed in Homecoming


We wandered down to the cave around 10am, making it to the entrance around 12pm. The entrance series went pretty fast ... at least it did for me as Harry and Charlotte were carrying the heavy bags. On the way we made a quick stop to rerig a knot pass. As we reached the bottom of Wallace and Grommit where they had finished rigging the previous day both me and Harry experienced fizzling as we glazed the dry rope on the final 55m. I did not enjoy this part. We then made our way up the climb and Harry began rigging the small pitches before the long pitch series. The most notable part of this was when Harry appeared the wrong side of a pitch head after following the description and getting lost. It was highly amusing. As Charlotte began rigging the final pitch series me and Harry huddled in a shelter and watched Mathilda the musical. As it came to replacing a bolt Harry left to go help Charlotte and I was left along ... until it was discovered the drill battery was dead and I had to come down with a back up. As 8pm approached we made our way out leaving rope to finish the final part of rigging. On the way out we made a noodle stop at the top of Wallace and Grommit but with no fork a knife had to suffice. The walk back was miserable as we started following new reflectors towards the col and had to turn around and start again. We finally made it back to top camp at 1 am.
T/U: 0.0 hours
@@ -466,7 +466,7 @@ Bolts used:
2023-07-07
-
Will Kay, Amelia Oliver, Ashley Gregg, Alice Kirby,
+
Alice Kirby, Will Kay, Ashley Gregg, Amelia Oliver,
Fishface – Surveying Stalagtite Loop to the top of Liquid Luck


Following our previous shallow trip into Fishface we set out to continue the previous combined leads from the day before. The journey across the plateau and into the cave was uneventful, aside from @@ -505,7 +505,7 @@ camp for curry.
2023-07-08
-
Amelia Oliver, Jonty Pine, Ashley Gregg, Janis Huns,
+
Janis Huns, Amelia Oliver, Ashley Gregg, Jonty Pine,
Fishface - Pushing deep Fishface


We went to where the group - Honorata, Radost and Mike - had left off the previous day. European Federalists was a very tough traverse that required the use of ascenders. We bolted and rigged (my first bolt!) the pitch at the end of the muddy passage about 10m down to a choke of boulders. At the bottom of it we saw that there is a large, smooth crack continuing east leading to a large, deep chamber that seems to be the same one we saw on the right. If it is it would be elongated roughly in a north east to south west direction. Ash and Mealy also explored some C leads which did not go far.
T/U: 0.0 hours
@@ -514,7 +514,7 @@ camp for curry.
2023-07-08
-
Will Kay, Mike Butcher, Emma Caspers,
+
Will Kay, Emma Caspers, Mike Butcher,
Following up on Radost and Honorata's prospecting leads (Unlucky Gemse cave and Amphitheatre cave)


Radost and Honorata had found a few promising entrances that they could explore with just a hand line on Thursday so me, Mike and Emma set out with caving kit, bolting kit and a few ropes to investigate further (ropes were slightly out of date ones - ropes were very hard to come by at Top Camp so we pinched two 20m ropes from the stash at Fishface entrance).

@@ -535,7 +535,7 @@ We then walked back, attempting but failing to find Garlic Cave. On the way back
2023-07-08
-
Sarah Parker, Harry Kettle, Charlotte Payne,
+
Sarah Parker, Charlotte Payne, Harry Kettle,
Beyond Watershed in Homecoming - Flowstone Canyon


I carried my caving and bivvy gear from Top Camp to Homecoming whilst Harry and Charlotte carried Top Camp's second shovel and a camp bed (surprisingliy heavy) to be picked up by the Garlic Cave people.

@@ -561,7 +561,7 @@ Slow, tired progress out saw us leave the cave at about 1am. I stumbled to Garli
2023-07-10
-
Buck Blake, Joel Stobbart, Janis Huns, Jonty Pine, Amelia Oliver, Ashley Gregg,
+
Jonty Pine, Ashley Gregg, Buck Blake, Janis Huns, Amelia Oliver, Joel Stobbart,
Fishface - Carrying rope to Fishface and failing to rig entrance


After the complicated operation of handling the solar panels out of the storage cave, the six of us packed kit, rope and a rescue bag and set @@ -581,7 +581,7 @@ left our caving kit under the small overhand near the entrance, then headed back
2023-07-10
-
Harry Kettle, Wassil Janssen, Merryn Matthews, Christian Kuhlmann, Alice Kirby,
+
Christian Kuhlmann, Harry Kettle, Merryn Matthews, Alice Kirby, Wassil Janssen,
Homecoming - Rig faff


This was a long day that started at Base Camp with the intent of rigging the entrance series of Homecoming cave. We set off only slightly later than the planned 8:00 [illegible] some faff. Quite surprising since Will was not part of the team.

The walk up to Top Camp took almost 2 hours, just as expected. We were also carrying drills, string, food, and other Top Camp equipment. We followed the reflectors to Fishface. They were white on both sides, which could make it frustrating if you are someone who is lost on their way to Top Camp. Instead, it should be red that leads to Top Camp, while white leads to caves and the car park. @@ -603,7 +603,7 @@ left our caving kit under the small overhand near the entrance, then headed back
2023-07-10
-
Harry Kettle, Honorata Bogusz, Christian Kuhlmann, Radost Waszkiewicz, Ashley Gregg,
+
Radost Waszkiewicz, Harry Kettle, Christian Kuhlmann, Ashley Gregg, Honorata Bogusz,
festering - Canyoning Strubklamm


We went canyoning on a rest day. The "Strubklamm" canyon is located near Saltzburg, approximately 1h of driving from Bad Aussee. The canyon is graded V1A3 (vertical 1, aquatic 3). It's very aquatic, with many small jumps available and a 300m swimming passage. There are 2 bigger jumps: approx. 8m and 10m, both can be abseiled (topo can be found online). @@ -639,7 +639,7 @@ Yet again a black bmw slowly moved in front of us with their sign flashing. Anot
2023-07-11
-
Radost Waszkiewicz, Thomas Phillips, Ashley Gregg, Ely Brookes, James Waite, Will Kay, Philip Sargent, Honorata Bogusz,
+
Ely Brookes, Will Kay, Honorata Bogusz, Radost Waszkiewicz, Ashley Gregg, James Waite, Philip Sargent, Thomas Phillips,
plateau - walk in


Will and Phil went up as part of the 2-car lift to the carpark. Others all going to topcamp. Cardiff contingent (Ely, James, Thomas) arrived the previous day (at last)

Dep. carpark 10:06 we walked to the col but got spread out, 2 Cardiffians particularly heavily loaded so Ash dropped back to accompany them while James headed on with Radost and Honorata. @@ -737,7 +737,7 @@ his own logbook entry.
2023-07-11
-
Honorata Bogusz, Ashley Gregg, Radost Waszkiewicz,
+
Honorata Bogusz, Radost Waszkiewicz, Ashley Gregg,
2023-hbrw-03 - Dropping Amphitheater Hoehle


The Amphitheater Hoehle is named after its entrance which resembles an amphitheather. There are a couple of meters of an easy climb from the very top to the boulder where we started rigging. Rigging starts with an approx. 5-6m down climb, where we put a handline. It's followed by a traverse (10m ?) above the entrance to the first pitch. We rigged a Y-hang at the end of the traverse. @@ -749,7 +749,7 @@ The Amphitheater Hoehle is named after its entrance which resembles an amphithea
2023-07-12
-
Lizzie Caisley, Merryn Matthews, Emily Mabbett, Joel Stobbart, Thomas Phillips,
+
Joel Stobbart, Lizzie Caisley, Emily Mabbett, Thomas Phillips, Merryn Matthews,
plateau - Prospecting between Homecoming and Fishface


We started by walking to Unlucky Gamse Cave, mostly following the path to homecoming, as Radost and Honorata had told us there was an exciting looking entrance around 50m North of it - Rose Blumen Hoehle (2023-hbrw-05). We dismissed their cave as choked by snow, however it @@ -803,7 +803,7 @@ was no evidence of a draft at the bottom so not particularly promising.
2023-07-13
-
Honorata Bogusz, Radost Waszkiewicz, Oakem Kyne, Charlotte Payne, Harry Kettle, Jonty Pine,
+
Charlotte Payne, Jonty Pine, Honorata Bogusz, Oakem Kyne, Harry Kettle, Radost Waszkiewicz,
festering - Via Ferrata "Panorama Kletterstieg Sisi"


@@ -871,7 +871,7 @@ Pic: Oakem Kyne (L) and Jonty Pine (R) on Panorama Kletterstieg Sisi.
2023-07-14
-
James Waite, Charlotte Payne, Harry Kettle,
+
James Waite, Harry Kettle, Charlotte Payne,
Homecoming - pushing down homecoming from flowstone canyon, into alpine showers


after a heavy afternoon at the tatty hut, Harry asked if i fancied coming to @@ -975,7 +975,7 @@ Full details at
2023-07-15
-
Ashley Gregg, Janis Huns, Ely Brookes,
+
Ashley Gregg, Ely Brookes, Janis Huns,
Prospecting - Up to Homecoming from the Col

Edit this entry @@ -999,7 +999,7 @@ drops approx. 5m with snow at bottom, can't clearly see bottom from top.
2023-07-15
-
Ashley Gregg, Philip Balister, Ely Brookes,
+
Ashley Gregg, Ely Brookes, Philip Balister,
2018-DM-04 – Prospecting, exploring, surveying


The plan was to do some prospecting in the relatively uncovered area around Garlic cave. We met up with Philip at Garlic cave and had some noodles for lunch. Philip had already done some @@ -1023,7 +1023,7 @@ Top Camp.
2023-07-15
-
Christian Kuhlmann, Merryn Matthews, Wassil Janssen,
+
Christian Kuhlmann, Wassil Janssen, Merryn Matthews,
In Search Of Salamanders


Following an impromptu pre-expo-dinner dinner the night before, we all arose with a tinge of hangover. Rapidly consuming breakfast and packing kit, we set off at 8:30am, only 30 minutes after we said we would which in all fairness is very good going for us. After a smooth ferry to the Loser car park by our wonderful chauffer Alice, we set off up the mountain. Almost immediately after we left the car park a navigational faux pas was made and we found ourselves on the wrong path, and with a small section of off-roading, we headed up to top camp with no other further obstructions. @@ -1116,7 +1116,7 @@ Walk from Gschwandt Alm below the western edge of the plateau.
2023-07-17
-
Ashley Gregg, Oakem Kyne, Emma Caspers,
+
Ashley Gregg, Emma Caspers, Oakem Kyne,
Fishface – Kresh connection confusion


Joel had discovered an exciting new cave, unfortunately this left us with rope and a lead down in the Fish face – happy butterfly connection which no-one was going to. I was naively convinced to go and @@ -1228,7 +1228,7 @@ Despite ~300m of passage being surveyed minimal progress was made in getting clo
2023-07-18
-
Ashley Gregg, Merryn Matthews, Zac Woodford,
+
Ashley Gregg, Zac Woodford, Merryn Matthews,
Tempest – Touch of Death


Excited to go and see (and kill) this new cave, two groups set off to push the two ‘A’ leads at the extent of Tempest. Arriving at the cave we prepared the bags and realised we’d forgotten a hammer, so @@ -1329,7 +1329,7 @@ we regrouped at the base of the heifer after adding a bolt to make the pitch hea
2023-07-19
-
Ashley Gregg, Will Kay, Zac Woodford,
+
Ashley Gregg, Zac Woodford, Will Kay,
Prospecting – dropping Boring Hole and Dead on Arrival


Took Zac to bolt a cave he had previously found not too far of the path to Fishface. Slight delay as we’d forgotten something important (bolts maybe?), which Will went back to get. @@ -1474,7 +1474,7 @@ Test of the logbook entry editing system.
2023-07-29
-
Joe Stell, Nadia Raeburn, Frank Tully,
+
Joe Stell, Frank Tully, Nadia Raeburn,
Maelstrom - "Undersold"




Hearing of our great success with this shallow lead, Nadia, having returned from a fishface camping trip the day before, decided she would join us for a nice easy surface trip. @@ -1535,7 +1535,7 @@ Also, it is now our closest cave passage to the neighbouring Schönberg system (
2023-07-31
-
Ben Chaddock, Adam Erskine, Charlie Crossley,
+
Ben Chaddock, Charlie Crossley, Adam Erskine,
Fishface - Cocoa Channel Bolting Mission
[Adam E = Adam Erskine-Jones]

On the morning of my first pushing trip deep within Fishgesicht (5 minutes from camp) the nervous energy carried me up and away through the bountiful faff. After triple checking that we had all the equipment for bolting and surveying a rift that apparently continued but was too sketchy without a traverse line, Charlie, Adam EJ and I traipsed across the plateau for even more faff outside the entrance. After a prompt descent we met the camping team who showed us the way to silverback scoop including a stop at the tap and going down a wrong branch. Squeezing through a pitch head to descend 7m into a dusty tight canyon with a tackle sack full of heavy gear, I did wonder what I was doing, but once the drill was out that fell away completely. @@ -1549,7 +1549,7 @@ Also, it is now our closest cave passage to the neighbouring Schönberg system (
2023-08-01
-
Nathan Walker, Adam Aldridge, Joe Stell, Manfred Wuits,
+
Manfred Wuits, Nathan Walker, Joe Stell, Adam Aldridge,
Surface - Hike to Garlic


Woke up to see a sub-optimal forecast. Somehow I was convinced that hiking to Garlic Cave Camp was of course the best course of action to take, so we set off. It was actually dry for the first third or so to fishface gear dump to collect our caving gear, but this did not last.
@@ -1561,7 +1561,7 @@ The path is a little treacherous in the wet, so it was a little slow going and s
2023-08-02
-
Nathan Walker, Adam Aldridge, Joe Stell, Manfred Wuits,
+
Manfred Wuits, Nathan Walker, Joe Stell, Adam Aldridge,
Surface - dropping Buzzard Hole on the northerly Kleiner Wildkogel ridge


After our lovely sleep, we woke up to dry weather, and set off to the top of the ridge above Garlic. Carrying the thick, wet 90m up the mountain was not conducive to good balance so I did fall over in the bunde and struggle to get back up. We reached Buzzard, Adam rigged it, first to the bunde, then hand bolting whilst we waited patiently. The improvement in weather did give a lovely backdrop of the Braeuningzinken for photos. Nathan went down next, followed by Manfred whilst I stayed outside enjoying the view. Upon Adam's return, I swiftly fell asleep, and awoke to find Nathan and Manfred had finished their survey and were finalising some sketches. Adam spotted some interesting holes beneath us on the plateau, so wet set off to prospect (see next entry).
T/U: 3.0 hours
@@ -1570,7 +1570,7 @@ The path is a little treacherous in the wet, so it was a little slow going and s
2023-08-02
-
Nathan Walker, Adam Aldridge, Joe Stell, Manfred Wuits,
+
Manfred Wuits, Nathan Walker, Joe Stell, Adam Aldridge,
Surface - Prospecting east and south of Kleiner Wildkogel


After dropping Buzzard, the fours of us decided to drop down the north face of the ridge, heading east at first, then split to form two prospecting groups, Adam and I heading further east to wrap around to the south face of the ridge, and Nathan and Manfred following the north face westwards to @@ -1611,7 +1611,7 @@ that seem to follow a rift along the base of Wildkogel, then back to Garlic camp
2023-08-03
-
Adam Aldridge, Joe Stell, Becka Lawson, Nathan Walker, David Botcherby,
+
Nathan Walker, Adam Aldridge, Becka Lawson, David Botcherby, Joe Stell,
Garlic - Fettling


Woke up. 10am. It was rainy :( @@ -1723,7 +1723,7 @@ The pitches back to camp were painful, despite their shortness, due to our mud c
2023-08-10
-
Rob Watson, Nadia Raeburn, Kai Trusson, David Botcherby,
+
David Botcherby, Rob Watson, Kai Trusson, Nadia Raeburn,
fishface - Connecting FF to SMK: a step further
Blog Author: nobrotson
Connecting FF to SMK - a step further
@@ -1845,7 +1845,958 @@ All in all, this was a fun project to have for our last week. It was a shame not
2023-11-15
Philip Sargent,
Cambridge - TESTING
-We had a lot of fun... +import subprocess +from datetime import datetime +from pathlib import Path + +from django import forms +from django.core.files.storage import FileSystemStorage +from django.shortcuts import render, redirect + +import settings + +from troggle.core.models.caves import GetCaveLookup +from troggle.core.models.logbooks import LogbookEntry, writelogbook, PersonLogEntry +from troggle.core.models.survex import DrawingFile +from troggle.core.models.troggle import DataIssue, Expedition, PersonExpedition +from troggle.core.utils import alphabet_suffix, current_expo, sanitize_name, unique_slug +from troggle.parsers.people import GetPersonExpeditionNameLookup, known_foreigner + +# from databaseReset import reinit_db # don't do this. databaseRest runs code *at import time* + +from .auth import login_required_if_public + +"""File upload 'views' +Note that there are other file upload forms in views/wallet_edit.py +and that core/forms.py contains Django class-based forms for caves and entrances. +""" + +todo = """ +- Ideally we should validate uploaded file as being a valid file type, not a dubious script or hack + Validate image files using a magic recogniser in walletedit() + https://pypi.org/project/reportlab/ or + https://stackoverflow.com/questions/889333/how-to-check-if-a-file-is-a-valid-image-file + +- Write equivalent GPX upload form system, similar to walletedit() but in expofiles/gpslogs/ + Need to validate it as being a valid GPX file using an XML parser, not a dubious script or hack + +- Validate Tunnel & Therion files using an XML parser in dwgupload(). Though Julian says + tunnel is only mostly correct XML, and it does fail at least one XML parser. + +- parse the uploaded drawing file for links to wallets and scan files as done + in parsers/drawings.py + +- Enable folder creation in dwguploads or as a separate form + +- Enable file rename on expofiles, not just for /surveyscans/ (aka wallets) + +- Make file rename utility less ugly. +""" + +def create_new_lbe_slug(date): + onthisdate = LogbookEntry.objects.filter(date=date) + n = len(onthisdate) + print(f" Already entries on this date: {n}\n {onthisdate}") + + suffix = alphabet_suffix(n+1) + + tid = f"{date}{suffix}" + print(tid) + return tid + +def store_edited_entry_into_database(date, place, title, text, others, author, tu, slug): + """saves a single logbook entry and related personlogentry items + + need to select out *guest and foreign friends from others + + Rather similar to similarly named function in parsers/logbooks but circular reference prevents us using it directly, + and they need refactoring anyway. + """ + + year = slug[0:4] + expedition = Expedition.objects.get(year=year) + cave = GetCaveLookup().get(place.lower()) + # print(f"{place} {cave=}") + + if LogbookEntry.objects.filter(slug=slug).exists(): + # oops. + message = f" ! - DUPLICATE SLUG for logbook entry {date} - {slug}" + DataIssue.objects.create(parser="logbooks", message=message) + slug = slug + "_" + unique_slug(text,2) + + otherAttribs = { + "place": place, + "text": text, + "expedition": expedition, + "time_underground": tu, + "cave_slug": str(cave), + "title": f"{place} - {title}", + # "other_people": others + } + coUniqueAttribs = {"slug": slug, "date": date } + + lbo = LogbookEntry.objects.create(**otherAttribs, **coUniqueAttribs) + + pt_list = [] + # These entities have to be PersonExpedition objects + team = others.split(",") + team.append(author) + + odds = [] + for name in team: + name = name.strip() + if len(name) > 0: + if name[0] == "*": # a name prefix of "*" is special, just a string. + odds.append(name) + print(f" - adding * special name '{name}'") + else: + try: + personyear = GetPersonExpeditionNameLookup(expedition).get(name.lower()) + if not personyear: + odds.append(name) + print(f" - adding unrecognised expoer '{name}'") + if known_foreigner(name): + message = f" ! - Known foreigner: '{name}' in entry {slug=}" + print(message) + else: + message = f" ! - No name match for: '{name}' in entry {slug=}" + print(message) + DataIssue.objects.create(parser="logbooks", message=message) + else: + coUniqueAttribs = {"personexpedition": personyear, "nickname_used": name, "logbook_entry": lbo} # lbo is primary key + otherAttribs = {"time_underground": tu, "is_logbook_entry_author": (name==author)} + pt_list.append(PersonLogEntry(**otherAttribs, **coUniqueAttribs)) + + except: + # This should not happen. We do not raise exceptions in that function + message = f" ! - EXCEPTION: '{name}' in entry {slug=}" + print(message) + DataIssue.objects.create(parser="logbooks", message=message) + raise + + PersonLogEntry.objects.bulk_create(pt_list) + + lbo.other_people = ", ".join(odds) + print(f" - Saving other_people '{lbo.other_people}'") + lbo.save() + +class FilesForm(forms.Form): # not a model-form, just a form-form + uploadfiles = forms.FileField() + +class FilesRenameForm(forms.Form): # not a model-form, just a form-form + uploadfiles = forms.FileField() + renameto = forms.CharField(strip=True, required=False) + +class TextForm(forms.Form): # not a model-form, just a form-form + photographer = forms.CharField(strip=True) + +class ExpofileRenameForm(forms.Form): # not a model-form, just a form-form + renameto = forms.CharField(strip=True, required=False) + +class LogbookEditForm(forms.Form): # not a model-form, just a form-form + author = forms.CharField(strip=True, required=False) + +@login_required_if_public +def logbookedit(request, year=None, slug=None): + """Edit a logbook entry + + This 'validates' the author as being on expo in the current year, but only indicates this by + putting the text of the form prompt in red (same as for an invalid date, which is arguably more important). + No check is done on the other people on the trip as this is picked up anyway by parsing on import + and we don't really care at this point. + + If the author name is mispelled, noticed, and chnaged, then two logbook entries are created + with sequential slugs ...b ...c etc. This is because we are doing validation on GET not on POST + and we are not rewriting the URL when a slug gets set. Hmm. + """ + def validate_year(year): + try: + expo = Expedition.objects.get(year=year) + except: + year = current_expo() + return year + + def new_entry_form(): + return render( + request, + "logbookform.html", + { + "form": form, + "year": year, + }, + ) + def clean_tu(tu): + if tu =="": + return 0 + try: + tu = float(tu)/1 # check numeric + except: + return 0 + return tu + + if not year: + if not slug: + year = current_expo() + else: + year = slug[0:4] + try: + year = str(int(year)) + except: + year = current_expo() + + author = "" + + if request.method == "POST": + form = LogbookEditForm(request.POST) + if not form.is_valid(): + message = f'Invalid form response for logbook entry creating "{request.POST}"' + print(message) + return render(request, "errors/generic.html", {"message": message}) + else: + # if there is no slug then this is probably a completely new lbe and we need to enter it into the db + # otherwise it is an update + # validation all to be done yet.. + date = request.POST["date"].strip() + author = request.POST["author"].strip() # TODO check against personexpedition on submit + others = request.POST["others"].strip() # TODO check each against personexpedition on submit + place = request.POST["place"].strip().replace(' - ',' = ') # no hyphens ! + title = request.POST["title"].strip() + entry = request.POST["text"].strip() + entry = entry.replace('\r','') # remove HTML-standard CR inserted from form. + # entry = entry.replace('\n\n','\n
\n
\n') # replace 2 \n with

+ # entry = entry.replace('

','
\n tag with

+ # entry = entry.replace('

\n tag with attributes with

+ # entry = entry.replace('
','
') # clean up previous hack + tu = request.POST["tu"].strip() + tu = clean_tu(tu) + + try: + odate = datetime.strptime(date.replace(".", "-"), "%Y-%m-%d").date() + dateflag = False + except: + odate = datetime.strptime(f"{year}-01-01", "%Y-%m-%d").date() + print(f"! Invalid date string {date}, setting to {odate}") + dateflag = True + date = odate.isoformat() + + year = validate_year(year) + expo = Expedition.objects.get(year=year) + personyear = GetPersonExpeditionNameLookup(expo).get(author.lower()) + if personyear: + authorflag = False + else: + authorflag = True + print(f"! Unrecognised author: {author}") + + + if not slug: + # Creating a new logbook entry with all the gubbins + slug = create_new_lbe_slug(date) + else: + # OK we could patch the object in place, but if the people on the trip have changed this + # would get very messy. So we delete it and recreate it and all its links + print(f"- Deleting the LogBookEntry {slug}") + LogbookEntry.objects.filter(slug=slug).delete() + + print(f"- Creating the LogBookEntry {slug}") + year = slug[0:4] + try: + expedition = Expedition.objects.get(year=year) + except Expedition.DoesNotExist: + message = f'''! - This expo "{year}" not created yet + It needs to be created before you can save a logbook entry. + See /handbook/computing/newyear.html + + WHAT TO DO NOW: + 1. Press the Back button on your proswer to return to the screen where you typed up the entry, + 2. Copy the text of what you wrote into a new text file, + 3. Direct a nerd to fix this. It should take only a couple of minutes.''' + print(message) + return render(request, "errors/generic.html", {"message": message}) + store_edited_entry_into_database(date, place, title, entry, others, author, tu, slug) + + + print(f"- Rewriting the entire {year} logbook to disc ") + filename= "logbook.html" + try: + writelogbook(year, filename) # uses a template, not the code fragment below which is just a visible hint to logged on user + 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}) + + # Code fragment illustration - not actually what gets saved to database + output = f''' + +

{date}
+
{author}, {others}
+
{place} - {title}
+ +{entry} + +
T/U {tu} hrs
+
+ +''' + # Successful POST + # So save to database and then write out whole new logbook.html file + + # We do author validation on the form as displayed by GET, not at the moment of POST. + # If we had JS validation then we could be more timely. + git = settings.GIT + dirpath = Path(settings.EXPOWEB) / "years" / str(year) + lbe_add = subprocess.run( + [git, "add", filename], cwd=dirpath, capture_output=True, text=True + ) + msgdata = ( + lbe_add.stderr + + "\n" + + lbe_add.stdout + + "\nreturn code: " + + str(lbe_add.returncode) + ) + message = f'! - FORM Logbook Edit {slug} - Success: git ADD on server for this file {filename}.' + msgdata + print(message) + if lbe_add.returncode != 0: + msgdata = ( + "Ask a nerd to fix this.\n\n" + + lbe_add.stderr + + "\n\n" + + lbe_add.stdout + + "\n\nreturn code: " + + str(lbe_add.returncode) + ) + message = ( + f"! - FORM Logbook Edit - CANNOT git ADD on server for this file {filename}. {slug} edits saved but not added to git.\n" + + msgdata + ) + print(message) + return render(request, "errors/generic.html", {"message": message}) + + lbe_commit = subprocess.run( + [git, "commit", "-m", f"Logbook edited {slug}"], + cwd=dirpath, + capture_output=True, + text=True, + ) + message = f'! - FORM Logbook Edit - {filename}. {slug} edits saved, added to git, and COMMITTED.\n' + msgdata + print(message) + #This produces return code = 1 if it commits OK + if lbe_commit.returncode != 0: + msgdata = ( + "Ask a nerd to fix this.\n\n" + + lbe_commit.stderr + + "\n" + + lbe_commit.stdout + + "\nreturn code: " + + str(lbe_commit.returncode) + ) + message = ( + f"! - FORM Logbook Edit -Error code with git on server for {filename}. {slug} edits saved, added to git, but NOT committed.\n" + + msgdata + ) + print(message) + return render(request, "errors/generic.html", {"message": message}) + + return render( + request, + "logbookform.html", + { + "form": form, + "year": year, + "date": date, "dateflag": dateflag, + "author": author, "authorflag": authorflag, + "others": others, + "place": place, + "title": title, + "tu": tu, + "entry": entry, + "output": output, + "slug": slug, + }, + ) + # GET here + else: + form = LogbookEditForm() + year = validate_year(year) + + if not slug: # no slug or bad slug for an lbe which does not exist + return new_entry_form() + else: + lbes = LogbookEntry.objects.filter(slug=slug) + if not lbes: + return new_entry_form() + else: + if len(lbes) > 1: + return render(request, "object_list.html", {"object_list": lbes}) # ie a bug + else: + lbe = lbes[0] + print(f"{lbe}") + tu = clean_tu(lbe.time_underground) + + people = [] + for p in lbe.personlogentry_set.filter(logbook_entry=lbe): # p is a PersonLogEntry object + if p.is_logbook_entry_author: + # author = p.personexpedition.person.fullname + author = p.nickname_used + else: + # people.append(p.personexpedition.person.fullname) + people.append(p.nickname_used) + others =', '.join(people) + print(f"{others=}") + if lbe.other_people: + others = others + ", " + lbe.other_people + lenothers = min(70,max(20, len(others))) + print(f"{others=}") + + text = lbe.text + rows = max(5,len(text)/50) + return render( + request, + "logbookform.html", + { + "form": form, + "year": year, + "date": lbe.date.isoformat(), + "author": author, + "others": others, + "lenothers": lenothers, + "place": lbe.place, + "title": lbe.title.replace(f"{lbe.place} - ",""), + "tu": tu, + "entry": text, + "textrows": rows, + "slug": slug, + }, + ) + +@login_required_if_public +def expofilerename(request, filepath): + """Rename any single file in /expofiles/ - eventually. + Currently this just does files within wallets i.e. in /surveyscans/ + and it returns control to the original wallet edit page + """ + def is_rotatable(path): + """If file is a JPG but has no filename extension, then it must be renamed + before it can be rotated. + """ + print(f"{path}: '{Path(path).suffix.lower()}'") + if Path(path).suffix.lower() in [".png", ".jpg", ".jpeg"]: + return True + else: + return False + + def rotate_image(): + wallet = str(Path(filepath).parent).lstrip("surveyscans/") + cwd = settings.SCANS_ROOT / wallet + print(f"ROTATE \n{cwd=} \n{filename=}") + mogrify = settings.MOGRIFY + rot = subprocess.run( + [mogrify, "-rotate", "90", filename], cwd=cwd, capture_output=True, text=True + ) + msgdata = ( + rot.stderr + + "\n" + + rot.stdout + + "\nreturn code: " + + str(rot.returncode) + ) + message = f'! - ROTATE - Success: rotated this file {filename}.' + msgdata + print(message) + # DataIssue.objects.create(parser="mogrify", message=message) + + if rot.returncode != 0: + msgdata = ( + "Ask a nerd to fix this.\n\n" + + rot.stderr + + "\n\n" + + rot.stdout + + "\n\nreturn code: " + + str(rot.returncode) + ) + message = ( + f"! - ROTATE - CANNOT blurk for this file {filename}. \n" + + msgdata + ) + print(message) + DataIssue.objects.create(parser="mogrify", message=message) + + return simple_get() + + def simple_get(): + form = ExpofileRenameForm() + return render( + request, + "renameform.html", + { + "form": form, + "filepath": filepath, + "filename": filename, + "filesize": filesize, + "files": files, + "walletpath": walletpath, + "wallet": wallet, + "notpics": notpics, + "rotatable": rotatable, + }, + ) + + if filepath: + # using EXPOFILES not SCANS_ROOT in case we want to extend this to other parts of the system + actualpath = Path(settings.EXPOFILES) / Path(filepath) + else: + message = f'\n File to rename not specified "{filepath}"' + print(message) + return render(request, "errors/generic.html", {"message": message}) + + if not actualpath.is_file(): + message = f'\n File not found when attempting rename "{filepath}"' + print(message) + return render(request, "errors/generic.html", {"message": message}) + else: + filename = Path(filepath).name + walletpath = Path(filepath).parent + wallet = Path(walletpath).name + folder = actualpath.parent + filesize = f"{actualpath.stat().st_size:,}" + rotatable= is_rotatable(filename) + + + if not actualpath.is_relative_to(Path(settings.SCANS_ROOT)): + message = f'\n Can only do rename within wallets (expofiles/surveyscans/) currently, sorry. "{actualpath}" ' + print(message) + return render(request, "errors/generic.html", {"message": message}) + + files = [] + dirs = [] + notpics =[] + dirpath = actualpath.parent + print(f'! - FORM rename expofile - start \n{filepath=} \n{dirpath=} \n{walletpath=}') + if dirpath.is_dir(): + try: + for f in dirpath.iterdir(): + if f.is_dir(): + for d in f.iterdir(): + dirs.append(f"{f.name}/{d.name}") + if f.is_file(): + if is_rotatable(f.name): # should allow all images here which can be thumsized, not just rotatables. e.g. PDF + files.append(f.name) + else: + notpics.append(f.name) + except FileNotFoundError: + files.append( + "(Error. There should be at least one filename visible here. Try refresh.)" + ) + if request.method == "GET": + return simple_get() + + elif request.method == "POST": + form = ExpofileRenameForm(request.POST) + if not form.is_valid(): + message = f'Invalid form response for file renaming "{request.POST}"' + print(message) + return render(request, "errors/generic.html", {"message": message}) + + if "rotate" in request.POST: + return rotate_image() + + if "rename" in request.POST: + if "renametoname" not in request.POST: + print("renametoname not in request.POST") + # blank filename passed it, so just treat as another GET + return simple_get() + + + renameto = sanitize_name(request.POST["renametoname"]) + if (folder / renameto).is_file() or (folder / renameto).is_dir(): + rename_bad = renameto + message = f'\n Cannot rename to an existing file or folder. "{filename}" -> "{(folder / renameto)}"' + print(message) + return render( + request, + "renameform.html", + { + "form": form, + "filepath": filepath, + "filename": filename, + "filesize": filesize, + "files": files, + "walletpath": walletpath, + "wallet": wallet, + "notpics": notpics, + "rename_bad": rename_bad, + }, + ) + + actualpath.rename((folder / renameto)) + message = f'\n RENAMED "{filename}" -> "{(folder / renameto)}"' + print(message) + walletid = actualpath.relative_to(Path(settings.SCANS_ROOT)).parent.stem.replace("#",":") + print(walletid) + return redirect(f'/survey_scans/{walletid}/') + + else: # not GET or POST + print("UNRECOGNIZED action") + return simple_get() + +@login_required_if_public +def photoupload(request, folder=None): + """Upload photo image files into /expofiles/photos/// + This does NOT use a Django model linked to a Django form. Just a simple Django form. + You will find the Django documentation on forms very confusing, This is simpler. + + + When uploading from a phone, it is useful to be able to rename the file to something + meaningful as this is difficult to do on a phone. Previously we had assumed files would + be renamed to something useful before starting the upload. + Unfortunately this only works when uploading one file at at time , + inevitable once you think about it. + + Pending generic file renaming capability more generally. + """ + year = settings.PHOTOS_YEAR + filesaved = False + actual_saved = [] + + context = {"year": year, "placeholder": "AnathemaDevice"} + + yearpath = Path(settings.PHOTOS_ROOT, year) + + if folder == str(year) or folder == str(year) + "/": + folder = None + + if folder is None: + folder = "" # improve this later + dirpath = Path(settings.PHOTOS_ROOT, year) + urlfile = f"/expofiles/photos/{year}" + urldir = f"/photoupload/{year}" + else: # it will contain the year as well as the photographer + dirpath = Path(settings.PHOTOS_ROOT, folder) + if dirpath.is_dir(): + urlfile = f"/expofiles/photos/{folder}" + urldir = Path("/photoupload") / folder + else: + folder = "" # improve this later + dirpath = Path(settings.PHOTOS_ROOT, year) + urlfile = f"/expofiles/photos/{year}" + urldir = f"/photoupload/{year}" + + form = FilesRenameForm() + formd = TextForm() + + if request.method == "POST": + if "photographer" in request.POST: + formd = TextForm(request.POST) + if formd.is_valid(): + newphotographer = sanitize_name(request.POST["photographer"]) + try: + (yearpath / newphotographer).mkdir(exist_ok=True) + except: + message = f'\n !! Permissions failure ?! 0 attempting to mkdir "{(yearpath / newphotographer)}"' + print(message) + return render(request, "errors/generic.html", {"message": message}) + + else: + form = FilesRenameForm(request.POST, request.FILES) + if form.is_valid(): + f = request.FILES["uploadfiles"] + multiple = request.FILES.getlist("uploadfiles") + # NO CHECK that the files being uploaded are image files + fs = FileSystemStorage(dirpath) + + renameto = sanitize_name(request.POST["renameto"]) + + actual_saved = [] + if multiple: + if len(multiple) == 1: + if renameto != "": + try: # crashes in Django os.chmod call if on WSL, but does save file! + saved_filename = fs.save(renameto, content=f) + except: + print( + f'\n !! Permissions failure ?! 1 attempting to save "{f.name}" in "{dirpath}" {renameto=}' + ) + if "saved_filename" in locals(): + if saved_filename.is_file(): + actual_saved.append(saved_filename) + filesaved = True + else: # multiple is the uploaded content + try: # crashes in Django os.chmod call if on WSL, but does save file! + saved_filename = fs.save(f.name, content=f) + except: + print( + f'\n !! Permissions failure ?! 2 attempting to save "{f.name}" in "{dirpath}" {renameto=}' + ) + if "saved_filename" in locals(): + if saved_filename.is_file(): + actual_saved.append(saved_filename) + filesaved = True + else: # multiple is a list of content + for f in multiple: + try: # crashes in Django os.chmod call if on WSL, but does save file! + saved_filename = fs.save(f.name, content=f) + except: + print( + f'\n !! Permissions failure ?! 3 attempting to save "{f.name}" in "{dirpath}" {renameto=}' + ) + if "saved_filename" in locals(): + if saved_filename.is_file(): + actual_saved.append(saved_filename) + filesaved = True + files = [] + dirs = [] + try: + for f in dirpath.iterdir(): + if f.is_dir(): + dirs.append(f.name) + if f.is_file(): + files.append(f.name) + except FileNotFoundError: + files.append("(no folder yet - would be created)") + if len(files) > 0: + files = sorted(files) + + if dirs: + dirs = sorted(dirs) + + return render( + request, + "photouploadform.html", + { + "form": form, + **context, + "urlfile": urlfile, + "urldir": urldir, + "folder": folder, + "files": files, + "dirs": dirs, + "filesaved": filesaved, + "actual_saved": actual_saved, + }, + ) + + +@login_required_if_public +def dwgupload(request, folder=None, gitdisable="no"): + """Upload DRAWING files (tunnel or therion) into the upload folder in :drawings + AND registers it into the :drawings: git repo. + + This does NOT use a Django model linked to a Django form. Just a simple Django form. + You will find the Django documentation on forms very confusing, This is simpler. + + We could validate the uploaded files as being a valid files using an XML parser, not a dubious script or hack, + but this won't work on Tunnel files as Tunnel does not produce exactly valid xml + + We use get_or_create instead of simply creating a new object in case someone uploads the same file + several times in one session, and expects them to be overwritten in the database. Although + the actual file will be duplicated in the filesystem with different random name ending. + """ + + def dwgvalid(name): + if name in [ + ".gitignore", + ]: + return False + if Path(name).suffix.lower() in [".xml", ".th", ".th2", "", ".svg", ".txt"]: + return True # dangerous, we should check the actual file binary signature + return False + + def dwgvaliddisp(name): + """OK to display, even if we are not going to allow a new one to be uploaded""" + if name in [ + ".gitignore", + ]: + return False + if Path(name).suffix.lower() in [ + ".xml", + ".th", + ".th2", + "", + ".svg", + ".txt", + ".jpg", + ".jpeg", + ".png", + ".pdf", + ".top", + ".topo", + ]: + return True # dangerous, we should check the actual file binary signature + return False + + filesaved = False + actual_saved = [] + refused = [] + doesnotexist = "" + # print(f'! - FORM dwgupload - start "{folder}" - gitdisable "{gitdisable}"') + if folder is None: + folder = "" # improve this later + dirpath = Path(settings.DRAWINGS_DATA) + urlfile = "/dwgdataraw" + urldir = "/dwgupload" + else: + dirpath = Path(settings.DRAWINGS_DATA, folder) + urlfile = Path("/dwgdataraw/") / folder + urldir = Path("/dwgupload/") / folder + + form = FilesForm() + + if request.method == "POST": + form = FilesForm(request.POST, request.FILES) + if form.is_valid(): + # print(f'! - FORM dwgupload - POST valid: "{request.FILES["uploadfiles"]}" ') + f = request.FILES["uploadfiles"] + multiple = request.FILES.getlist("uploadfiles") + savepath = Path(settings.DRAWINGS_DATA, folder) + fs = FileSystemStorage(savepath) + + actual_saved = [] + refused = [] + + # GIT see also core/views/expo.py editexpopage() + # GIT see also core/models/cave.py writetrogglefile() + if gitdisable != "yes": # set in url 'dwguploadnogit/' + git = settings.GIT + else: + git = "echo" + # print(f'git DISABLED {f.name}') + + if multiple: + for f in multiple: + # print(f'! - FORM dwgupload - file {f} in {multiple=}') + if dwgvalid(f.name): + try: # crashes in Django os.chmod call if on WSL without metadata drvfs, but does save file! + saved_filename = fs.save(f.name, content=f) + except: + print( + f'! - FORM dwgupload - \n!! Permissions failure ?! on attempting to save file "{f.name}" in "{savepath}". Attempting to continue..' + ) + if "saved_filename" in locals(): + if Path(dirpath, saved_filename).is_file(): + actual_saved.append(saved_filename) + if gitdisable != "yes": + dr_add = subprocess.run( + [git, "add", saved_filename], cwd=dirpath, capture_output=True, text=True + ) + msgdata = ( + dr_add.stderr + + "\n" + + dr_add.stdout + + "\nreturn code: " + + str(dr_add.returncode) + ) + # message = f'! - FORM dwgupload - Success: git ADD on server for this file {saved_filename}.' + msgdata + # print(message) + if dr_add.returncode != 0: + msgdata = ( + "Ask a nerd to fix this.\n\n" + + dr_add.stderr + + "\n\n" + + dr_add.stdout + + "\n\nreturn code: " + + str(dr_add.returncode) + ) + message = ( + f"! - FORM dwgupload - CANNOT git ADD on server for this file {saved_filename}. Edits saved but not added to git.\n" + + msgdata + ) + print(message) + return render(request, "errors/generic.html", {"message": message}) + dwgfile, created = DrawingFile.objects.get_or_create( + dwgpath=saved_filename, dwgname=Path(f.name).stem, filesize=f.size + ) + dwgfile.save() + else: + message = f"! - FORM dwgupload - NOT A FILE {Path(dirpath, saved_filename)=}. " + print(message) + else: + message = f"! - FORM dwgupload - Save failure for {f.name}. Changes NOT saved." + print(message) + return render(request, "errors/generic.html", {"message": message}) + + if saved_filename != f.name: + # message = f'! - FORM dwgupload - Save RENAME {f.name} renamed as {saved_filename}. This is OK.' + # print(message) + pass + + else: + refused.append(f.name) + # print(f'REFUSED {f.name}') + + if actual_saved: + filesaved = True + if len(actual_saved) > 1: + dots = "..." + else: + dots = "" + if gitdisable != "yes": + dr_commit = subprocess.run( + [git, "commit", "-m", f"Drawings upload - {actual_saved[0]}{dots}"], + cwd=dirpath, + capture_output=True, + text=True, + ) + # message = f'! - FORM dwgupload - For uploading {actual_saved[0]}{dots}. Edits saved, added to git, and COMMITTED.\n' + msgdata + # print(message) + # This produces return code = 1 if it commits OK + if dr_commit.returncode != 0: + msgdata = ( + "Ask a nerd to fix this.\n\n" + + dr_commit.stderr + + "\n" + + dr_commit.stdout + + "\nreturn code: " + + str(dr_commit.returncode) + ) + message = ( + f"! - FORM dwgupload -Error code with git on server for this {actual_saved[0]}{dots}. Edits saved, added to git, but NOT committed.\n" + + msgdata + ) + print(message) + return render(request, "errors/generic.html", {"message": message}) + else: + print(f' git disabled "{git=}"') + else: # maybe all were refused by the suffix test in dwgvalid() + message = f"! - FORM dwgupload - Nothing actually saved. All were refused. {actual_saved=}" + print(message) + + files = [] + dirs = [] + # print(f'! - FORM dwgupload - start {folder=} \n"{dirpath=}" \n"{dirpath.parent=}" \n"{dirpath.exists()=}"') + try: + for f in dirpath.iterdir(): + if f.is_dir(): + if f.name not in [".git"]: + dirs.append(f.name) + continue + if f.is_file(): + if dwgvaliddisp(f.name): + files.append(f.name) + continue + except FileNotFoundError: + doesnotexist = True + if files: + files = sorted(files) + + if dirs: + dirs = sorted(dirs) + + return render( + request, + "dwguploadform.html", + { + "form": form, + "doesnotexist": doesnotexist, + "urlfile": urlfile, + "urldir": urldir, + "folder": folder, + "files": files, + "dirs": dirs, + "filesaved": filesaved, + "actual_saved": actual_saved, + "refused": refused, + }, + )
T/U: 0.0 hours