All the classes below inherit from the django class (models.Model) and are thereby made persistent in the database. All persistent instance variables are defined with e.g. "models.BooleanField()" types which is why they look so strange to a normal python programmer.
See the online auto-documentation for the current set of core objects and their instance variables and foreign keys (anything below with "models.ForeignKey" in the type field is a foreign key.
[This section to be moved elsewhere?]
We have a "Django management command" to find any fields on the models which are unused fossils. Do a full database import locally on a development machine, then in the /troggle/ folder run:
uv run manage.py find_empty_fields
This provides a report of which fields in the database are unused across all records. It was entirely written by Google Gemini AI in October 2025.
🚀 Starting scan for empty or null fields across all models... 🔍 Checking model: auth.Group ✅ All fields contain data. 🔍 Checking model: core.Expedition ✅ All fields contain data. 🔍 Checking model: core.Person -> Field 'orderref' is completely empty or null. 🔍 Checking model: core.PersonExpedition -> Field 'slugfield' is completely empty or null. 🔍 Checking model: core.QM -> Field 'completion_date' is completely empty or null. 🔍 Checking model: core.DrawingFile -> Field 'dwgcontains' is completely empty or null. -> Field 'survexfiles' is completely empty or null. 🔍 Checking model: core.Cave -> Field 'kataster_status' is completely empty or null. ...The code for this is located in find_empty_fields.py:
troggle/
└── core/
├── __init__.py
├── models/
├── management/
│ ├── __init__.py
│ └── commands/
│ ├── __init__.py
│ └── find_empty_fields.py <-- this file
└── views/
# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
# * Rearrange models' order
# * Make sure each model has one field with primary_key=True
# * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table
# Feel free to rename the models, but don't rename db_table values or field names.
#
# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [app_label]'
# into your database.
from django.db import models
class Area(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
new_since_parsing = models.BooleanField()
non_public = models.BooleanField()
short_name = models.CharField(max_length=100)
name = models.CharField(max_length=200, blank=True)
description = models.TextField(blank=True)
parent = models.ForeignKey ('self', blank=True, null=True)
class Cave(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
new_since_parsing = models.BooleanField()
non_public = models.BooleanField()
official_name = models.CharField(max_length=160)
kataster_code = models.CharField(max_length=20, blank=True)
kataster_number = models.CharField(max_length=10, blank=True)
unofficial_number = models.CharField(max_length=60, blank=True)
explorers = models.TextField(blank=True)
underground_description = models.TextField(blank=True)
equipment = models.TextField(blank=True)
references = models.TextField(blank=True)
survey = models.TextField(blank=True)
kataster_status = models.TextField(blank=True)
underground_centre_line = models.TextField(blank=True)
notes = models.TextField(blank=True)
length = models.CharField(max_length=100, blank=True)
depth = models.CharField(max_length=100, blank=True)
extent = models.CharField(max_length=100, blank=True)
survex_file = models.CharField(max_length=100, blank=True)
description_file = models.CharField(max_length=200, blank=True)
url = models.CharField(max_length=200, blank=True)
filename = models.CharField(max_length=200)
class CaveArea(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
cave_id = models.IntegerField()
area = models.ForeignKey (CoreArea)
class Caveandentrance(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
cave_id = models.IntegerField()
entrance_id = models.IntegerField()
entrance_letter = models.CharField(max_length=20, blank=True)
class Caveslug(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
cave_id = models.IntegerField()
slug = models.CharField(unique=True, max_length=50)
primary = models.BooleanField()
class Dataissue(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
new_since_parsing = models.BooleanField()
non_public = models.BooleanField()
date = models.DateTimeField()
parser = models.CharField(max_length=50, blank=True)
message = models.CharField(max_length=400, blank=True)
class Entrance(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
new_since_parsing = models.BooleanField()
non_public = models.BooleanField()
name = models.CharField(max_length=100, blank=True)
entrance_description = models.TextField(blank=True)
explorers = models.TextField(blank=True)
map_description = models.TextField(blank=True)
location_description = models.TextField(blank=True)
approach = models.TextField(blank=True)
underground_description = models.TextField(blank=True)
photo = models.TextField(blank=True)
marking = models.CharField(max_length=2)
marking_comment = models.TextField(blank=True)
findability = models.CharField(max_length=1, blank=True)
findability_description = models.TextField(blank=True)
alt = models.TextField(blank=True)
northing = models.TextField(blank=True)
easting = models.TextField(blank=True)
tag_station = models.TextField(blank=True)
exact_station = models.TextField(blank=True)
other_station = models.TextField(blank=True)
other_description = models.TextField(blank=True)
bearings = models.TextField(blank=True)
url = models.CharField(max_length=200, blank=True)
filename = models.CharField(max_length=200)
cached_primary_slug = models.CharField(max_length=200, blank=True)
class Entranceslug(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
entrance_id = models.IntegerField()
slug = models.CharField(unique=True, max_length=50)
primary = models.BooleanField()
class Expedition(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
new_since_parsing = models.BooleanField()
non_public = models.BooleanField()
year = models.CharField(unique=True, max_length=20)
name = models.CharField(max_length=100)
class Logbookentry(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
new_since_parsing = models.BooleanField()
non_public = models.BooleanField()
date = models.DateField()
expedition = models.ForeignKey (CoreExpedition, blank=True, null=True)
title = models.CharField(max_length=200)
cave_slug = models.CharField(max_length=50)
place = models.CharField(max_length=100, blank=True)
text = models.TextField()
slug = models.CharField(max_length=50)
filename = models.CharField(max_length=200, blank=True)
entry_type = models.CharField(max_length=50, blank=True)
class Person(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
new_since_parsing = models.BooleanField()
non_public = models.BooleanField()
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
fullname = models.CharField(max_length=200)
is_vfho = models.BooleanField()
mug_shot = models.CharField(max_length=100, blank=True)
blurb = models.TextField(blank=True)
orderref = models.CharField(max_length=200)
user_id = models.IntegerField(unique=True, blank=True, null=True)
class Personexpedition(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
new_since_parsing = models.BooleanField()
non_public = models.BooleanField()
expedition = models.ForeignKey (CoreExpedition)
person = models.ForeignKey (CorePerson)
slugfield = models.CharField(max_length=50, blank=True)
is_guest = models.BooleanField()
expo_committee_position = models.CharField(max_length=200, blank=True)
class PersonLogbookEntry(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
new_since_parsing = models.BooleanField()
non_public = models.BooleanField()
personexpedition = models.ForeignKey (CorePersonexpedition, blank=True, null=True)
time_underground = models.FloatField()
logbook_entry = models.ForeignKey (CoreLogbookentry)
is_logbook_entry_author = models.BooleanField()
class Qm(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
new_since_parsing = models.BooleanField()
non_public = models.BooleanField()
number = models.IntegerField()
grade = models.CharField(max_length=1)
location_description = models.TextField()
nearest_station_name = models.CharField(max_length=200, blank=True)
area = models.CharField(max_length=100, blank=True)
completion_description = models.TextField(blank=True)
resolution_station_name = models.CharField(max_length=200, blank=True, null=True)
area = models.CharField(max_length=100, blank=True, null=True)
page_ref = models.TextField(blank=True, null=True)
comment = models.TextField(blank=True, null=True)
class Survexblock(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
name = models.CharField(max_length=100)
parent = models.ForeignKey ('self', blank=True, null=True)
text = models.TextField()
cave_id = models.IntegerField(blank=True, null=True)
date = models.DateField(blank=True, null=True)
expedition_id = models.IntegerField(blank=True, null=True)
survexfile = models.ForeignKey ('CoreSurvexfile', blank=True, null=True)
begin_char = models.IntegerField()
survexpath = models.CharField(max_length=200)
survexscansfolder_id = models.IntegerField(blank=True, null=True)
totalleglength = models.FloatField()
class Survexfile(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
path = models.CharField(max_length=200)
primary = models.ForeignKey (
"SurvexFile", related_name="primarysurvex", blank=True, null=True, on_delete=models.SET_NULL
)
cave_id = models.IntegerField(blank=True, null=True)
class Survexpersonrole(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
survexblock = models.ForeignKey (CoreSurvexblock)
nrole = models.CharField(max_length=200, blank=True)
personname = models.CharField(max_length=100)
person_id = models.IntegerField(blank=True, null=True)
personexpedition_id = models.IntegerField(blank=True, null=True)
personlogbookentry_id = models.IntegerField(blank=True, null=True)
class Survexscansfolder(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
fpath = models.CharField(max_length=200)
walletname = models.CharField(max_length=200)
class Survexscansingle(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
ffile = models.CharField(max_length=200)
name = models.CharField(max_length=200)
survexscansfolder = models.ForeignKey (CoreSurvexscansfolder, blank=True, null=True)
class Survexstation(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
name = models.CharField(max_length=100)
block_id = models.IntegerField()
equate = models.ForeignKey (CoreSurvexequate, blank=True, null=True)
x = models.FloatField(blank=True, null=True)
y = models.FloatField(blank=True, null=True)
z = models.FloatField(blank=True, null=True)
class Dwgfile(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
dwgpath = models.CharField(max_length=200)
dwgname = models.CharField(max_length=200)
bfontcolours = models.BooleanField()
filesize = models.IntegerField()
npaths = models.IntegerField()
class DjangoAdminLog(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
action_time = models.DateTimeField()
object_id = models.TextField(blank=True)
object_repr = models.CharField(max_length=200)
action_flag = models.PositiveSmallIntegerField()
change_message = models.TextField()
content_type = models.ForeignKey ('DjangoContentType', blank=True, null=True)
user = models.ForeignKey (AuthUser)
class DjangoContentType(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
name = models.CharField(max_length=100)
app_label = models.CharField(max_length=100)
model = models.CharField(max_length=100)
class DjangoMigrations(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
app = models.CharField(max_length=255)
name = models.CharField(max_length=255)
applied = models.DateTimeField()
class DjangoRedirect(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
site = models.ForeignKey ('DjangoSite')
old_path = models.CharField(max_length=200)
new_path = models.CharField(max_length=200)
class DjangoSession(models.Model):
session_key = models.CharField(primary_key =True, max_length=40)
session_data = models.TextField()
expire_date = models.DateTimeField()
class DjangoSite(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
domain = models.CharField(max_length=100)
name = models.CharField(max_length=50)
class RegistrationRegistrationprofile(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
activation_key = models.CharField(max_length=40)
user = models.ForeignKey (AuthUser, unique=True)
class AuthGroup(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
name = models.CharField(unique=True, max_length=80)
class AuthGroupPermissions(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
group = models.ForeignKey(AuthGroup)
permission = models.ForeignKey('AuthPermission')
class AuthPermission(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
name = models.CharField(max_length=50)
content_type = models.ForeignKey('DjangoContentType')
codename = models.CharField(max_length=100)
class AuthUser(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
password = models.CharField(max_length=128)
last_login = models.DateTimeField()
is_superuser = models.BooleanField()
username = models.CharField(unique=True, max_length=30)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.CharField(max_length=75)
is_staff = models.BooleanField()
is_active = models.BooleanField()
date_joined = models.DateTimeField()
class AuthUserGroups(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
user = models.ForeignKey (AuthUser)
group = models.ForeignKey (AuthGroup)
class AuthUserUserPermissions(models.Model):
id = models.IntegerField(primary_key =True) # AutoField?
user = models.ForeignKey (AuthUser)
permission = models.ForeignKey (AuthPermission)