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 class Area(models.Model):
33 parent = models.ForeignKey(
37 on_delete=models.CASCADE,
39 name = models.CharField(max_length=64)
40 notes = models.TextField(blank=True)
43 if self.parent is None:
46 return '{} > {}'.format(self.parent, self.name)
48 class Boulder(models.Model):
49 area = models.ForeignKey('Area', on_delete=models.PROTECT)
50 name = models.CharField(max_length=64)
51 difficulty = models.CharField(
52 choices=BOULDER_DIFFICULTY_CHOICES,
55 mountainproject = models.URLField(blank=True)
56 notes = models.TextField(blank=True)
59 return '{} ({})'.format(self.name, self.difficulty)
61 PITCH_DIFFICULTY_CHOICES = (
103 class Pitch(models.Model):
104 order = models.PositiveSmallIntegerField()
105 route = models.ForeignKey(
107 on_delete=models.CASCADE,
108 related_name='pitches',
110 difficulty = models.CharField(
111 choices=PITCH_DIFFICULTY_CHOICES,
114 name = models.CharField(blank=True, max_length=32)
115 notes = models.TextField(blank=True)
118 ordering = ('order',)
119 verbose_name_plural = 'Pitches'
122 return 'P{} ({})'.format(self.order, self.difficulty)
124 PROTECTION_STYLE_CHOICES = (
126 ('toprope', 'Top Rope'),
130 class Route(models.Model):
131 area = models.ForeignKey('Area', on_delete=models.PROTECT)
132 name = models.CharField(max_length=64)
133 protection_style = models.CharField(max_length=8, choices=PROTECTION_STYLE_CHOICES)
134 mountainproject = models.URLField(blank=True)
135 notes = models.TextField(blank=True)
137 # TODO Write test for this
139 def difficulty(self):
140 return self.pitches.order_by('-difficulty').first().difficulty
143 return '{} ({})'.format(self.name, self.difficulty)
145 ATTEMPT_RESULT_CHOICES = (
150 PROTECTION_CHOICES = (
158 class Attempt(models.Model):
159 user = models.ForeignKey(User, on_delete=models.CASCADE)
160 date = models.DateField()
161 notes = models.TextField(blank=True)
162 boulder = models.ForeignKey('Boulder', null=True, on_delete=models.PROTECT, related_name='attempts')
163 route = models.ForeignKey('Route', null=True, on_delete=models.PROTECT, related_name='attempts')
164 result = models.CharField(max_length=8, choices=ATTEMPT_RESULT_CHOICES)
165 prior_knowledge = models.BooleanField(default=True)
166 protection_used = models.CharField(max_length=8, choices=PROTECTION_CHOICES)
170 models.CheckConstraint(
171 check=(Q(boulder__isnull=True) ^ Q(route__isnull=True)),
172 name='attempt_boulder_xor_route',
179 ('onsight', 'On Sight'),
181 ('project', 'Project'),
185 class Todo(models.Model):
186 user = models.ForeignKey(User, on_delete=models.CASCADE)
187 notes = models.TextField(blank=True)
188 protection = models.CharField(max_length=8, choices=PROTECTION_CHOICES)
189 boulder = models.ForeignKey('Boulder', null=True, on_delete=models.PROTECT, related_name='todos')
190 route = models.ForeignKey('Route', null=True, on_delete=models.PROTECT, related_name='todos')
191 style = models.CharField(max_length=8, choices=STYLE_CHOICES)
195 models.CheckConstraint(
196 check=(Q(boulder__isnull=True) ^ Q(route__isnull=True)),
197 name='todo_boulder_xor_route',
201 ordering = ('route__name',)
211 return '{} {}'.format(self.style, climb)