1 from django.contrib.auth.models import User
2 from django.core.exceptions import ValidationError
3 from django.db import models
4 from django.db.models import Q
7 def __xor__(self, other):
8 return (self & (~other)) | ((~self) & other)
12 BOULDER_DIFFICULTY_CHOICES = (
32 # TODO Provide a way of getting only Area objects which contain boulders/routes
33 class Area(models.Model):
34 parent = models.ForeignKey(
38 on_delete=models.CASCADE,
39 related_name='children',
41 name = models.CharField(max_length=64)
42 notes = models.TextField(blank=True)
45 if self.parent is None:
48 return '{} > {}'.format(self.parent, self.name)
50 class Boulder(models.Model):
51 area = models.ForeignKey(
53 on_delete=models.PROTECT,
54 related_name='boulders',
56 name = models.CharField(max_length=64)
57 difficulty = models.CharField(
58 choices=BOULDER_DIFFICULTY_CHOICES,
61 mountainproject = models.URLField(blank=True)
62 notes = models.TextField(blank=True)
65 return '{} ({})'.format(self.name, self.difficulty)
67 PITCH_DIFFICULTY_CHOICES = (
109 class Pitch(models.Model):
110 order = models.PositiveSmallIntegerField()
111 route = models.ForeignKey(
113 on_delete=models.CASCADE,
114 related_name='pitches',
116 difficulty = models.CharField(
117 choices=PITCH_DIFFICULTY_CHOICES,
120 name = models.CharField(blank=True, max_length=32)
121 notes = models.TextField(blank=True)
124 ordering = ('order',)
125 verbose_name_plural = 'Pitches'
128 return 'P{} ({})'.format(self.order, self.difficulty)
130 PROTECTION_STYLE_CHOICES = (
132 ('toprope', 'Top Rope'),
136 class Route(models.Model):
137 area = models.ForeignKey(
139 on_delete=models.PROTECT,
140 related_name='routes'
142 name = models.CharField(max_length=64)
143 protection_style = models.CharField(max_length=8, choices=PROTECTION_STYLE_CHOICES)
144 mountainproject = models.URLField(blank=True)
145 notes = models.TextField(blank=True)
147 # TODO Write test for this
149 def difficulty(self):
150 return self.pitches.order_by('-difficulty').first().difficulty
153 return '{} ({})'.format(self.name, self.difficulty)
155 ATTEMPT_RESULT_CHOICES = (
160 PROTECTION_CHOICES = (
168 class Attempt(models.Model):
169 user = models.ForeignKey(User, on_delete=models.CASCADE)
170 date = models.DateField()
171 notes = models.TextField(blank=True)
172 boulder = models.ForeignKey(
176 on_delete=models.PROTECT,
177 related_name='attempts',
179 route = models.ForeignKey(
183 on_delete=models.PROTECT,
184 related_name='attempts',
186 result = models.CharField(max_length=8, choices=ATTEMPT_RESULT_CHOICES)
187 prior_knowledge = models.BooleanField(default=True)
188 protection_used = models.CharField(max_length=8, choices=PROTECTION_CHOICES)
192 models.CheckConstraint(
193 check=(Q(boulder__isnull=True) ^ Q(route__isnull=True)),
194 name='attempt_boulder_xor_route',
201 ('onsight', 'On Sight'),
203 ('project', 'Project'),
207 class Todo(models.Model):
208 user = models.ForeignKey(User, on_delete=models.CASCADE)
209 notes = models.TextField(blank=True)
210 protection = models.CharField(max_length=8, choices=PROTECTION_CHOICES)
211 boulder = models.ForeignKey(
215 on_delete=models.PROTECT,
216 related_name='todos',
218 route = models.ForeignKey(
222 on_delete=models.PROTECT,
223 related_name='todos',
225 style = models.CharField(max_length=8, choices=STYLE_CHOICES)
229 models.CheckConstraint(
230 check=(Q(boulder__isnull=True) ^ Q(route__isnull=True)),
231 name='todo_boulder_xor_route',
235 ordering = ('route__name',)
245 return '{} {}'.format(self.style, climb)