eb521e15f06bfe7726c197c4f558d4465ce651e7
[zzz-pokedex.git] / pokedex / db / tables.py
1 # encoding: utf8
2
3 from sqlalchemy import Column, ForeignKey, MetaData, Table
4 from sqlalchemy.ext.declarative import declarative_base
5 from sqlalchemy.ext.associationproxy import association_proxy
6 from sqlalchemy.orm import backref, relation
7 from sqlalchemy.types import *
8 from sqlalchemy.databases.mysql import *
9
10 from pokedex.db import rst
11
12 metadata = MetaData()
13 TableBase = declarative_base(metadata=metadata)
14
15 class Ability(TableBase):
16 __tablename__ = 'abilities'
17 __singlename__ = 'ability'
18 id = Column(Integer, primary_key=True, nullable=False)
19 name = Column(Unicode(24), nullable=False)
20 flavor_text = Column(Unicode(64), nullable=False)
21 effect = Column(Unicode(255), nullable=False)
22
23 class ContestEffect(TableBase):
24 __tablename__ = 'contest_effects'
25 id = Column(Integer, primary_key=True, nullable=False)
26 appeal = Column(SmallInteger, nullable=False)
27 jam = Column(SmallInteger, nullable=False)
28 flavor = Column(Unicode(255), nullable=False)
29 effect = Column(Unicode(255), nullable=False)
30
31 class EggGroup(TableBase):
32 __tablename__ = 'egg_groups'
33 id = Column(Integer, primary_key=True, nullable=False)
34 name = Column(Unicode(16), nullable=False)
35
36 class Encounter(TableBase):
37 """Rows in this table represent encounters with wild Pokémon.
38
39 Within a given area in a given game, encounters are differentiated by the
40 slot they are in and a world condition.
41
42 Groups of slots belong to encounter types; these are what the player is
43 doing to get an encounter, such as surfing or walking through tall grass.
44
45 Within an encounter type, slots are defined primarily by rarity. Each slot
46 can also be affected by a world condition; for example, the 20% slot for
47 walking in tall grass is affected by whether a swarm is in effect in the
48 areas. "There is a swarm" and "there is not a swarm" are conditions, and
49 together they make a condition group. However, since "not a swarm" is a
50 base state rather than any sort of new state, it is omitted and instead
51 referred to by a NULL.
52
53 A slot (20% walking in grass) and single world condition (NULL, i.e. no
54 swarm) are thus enough to define a specific encounter.
55
56 Well, okay, almost: each slot actually appears twice.
57 """
58
59 __tablename__ = 'encounters'
60 id = Column(Integer, primary_key=True, nullable=False)
61 version_id = Column(Integer, ForeignKey('versions.id'), nullable=False, autoincrement=False)
62 location_area_id = Column(Integer, ForeignKey('location_areas.id'), nullable=False, autoincrement=False)
63 encounter_type_slot_id = Column(Integer, ForeignKey('encounter_type_slots.id'), nullable=False, autoincrement=False)
64 encounter_condition_id = Column(Integer, ForeignKey('encounter_conditions.id'), nullable=True, autoincrement=False)
65 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), nullable=False, autoincrement=False)
66 min_level = Column(Integer, nullable=False, autoincrement=False)
67 max_level = Column(Integer, nullable=False, autoincrement=False)
68
69 class EncounterCondition(TableBase):
70 """Rows in this table represent something different about the world that
71 can affect what Pokémon are encountered.
72 """
73
74 __tablename__ = 'encounter_conditions'
75 id = Column(Integer, primary_key=True, nullable=False)
76 encounter_condition_group_id = Column(Integer, ForeignKey('encounter_condition_groups.id'), primary_key=False, nullable=False, autoincrement=False)
77 name = Column(Unicode(64), nullable=False)
78
79 class EncounterConditionGroup(TableBase):
80 """Rows in this table represent a group of mutually exclusive conditions,
81 such as morning/day/night. "Conditions" that are part of the default state
82 of the world, such as "not during a swarm" or "not using the PokéRadar",
83 are not included in this table and are referred to by NULLs in other
84 tables.
85 """
86
87 __tablename__ = 'encounter_condition_groups'
88 id = Column(Integer, primary_key=True, nullable=False)
89 name = Column(Unicode(64), nullable=False)
90
91 class EncounterType(TableBase):
92 """Rows in this table represent ways the player can enter a wild encounter;
93 i.e. surfing, fishing, or walking through tall grass.
94 """
95
96 __tablename__ = 'encounter_types'
97 id = Column(Integer, primary_key=True, nullable=False)
98 name = Column(Unicode(64), nullable=False)
99
100 class EncounterTypeSlot(TableBase):
101 """Rows in this table represent an abstract "slot" within an encounter
102 type, associated with both a condition group and a rarity.
103
104 Note that there are two encounters per slot, so the rarities will only add
105 up to 50.
106 """
107
108 __tablename__ = 'encounter_type_slots'
109 id = Column(Integer, primary_key=True, nullable=False)
110 encounter_type_id = Column(Integer, ForeignKey('encounter_types.id'), primary_key=False, nullable=False, autoincrement=False)
111 encounter_condition_group_id = Column(Integer, ForeignKey('encounter_condition_groups.id'), primary_key=False, nullable=True, autoincrement=False)
112 rarity = Column(Integer, nullable=False, autoincrement=False)
113
114 class EvolutionChain(TableBase):
115 __tablename__ = 'evolution_chains'
116 id = Column(Integer, primary_key=True, nullable=False)
117 growth_rate_id = Column(Integer, ForeignKey('growth_rates.id'), nullable=False)
118 steps_to_hatch = Column(Integer, nullable=False)
119 baby_trigger_item = Column(Unicode(12))
120
121 class EvolutionMethod(TableBase):
122 __tablename__ = 'evolution_methods'
123 id = Column(Integer, primary_key=True, nullable=False)
124 name = Column(Unicode(64), nullable=False)
125 description = Column(Unicode(255), nullable=False)
126
127 class Generation(TableBase):
128 __tablename__ = 'generations'
129 id = Column(Integer, primary_key=True, nullable=False)
130 name = Column(Unicode(16), nullable=False)
131 main_region = Column(Unicode(16), nullable=False)
132
133 class GrowthRate(TableBase):
134 """`formula` is written in LaTeX math notation."""
135 __tablename__ = 'growth_rates'
136 id = Column(Integer, primary_key=True, nullable=False)
137 name = Column(Unicode(20), nullable=False)
138 formula = Column(Unicode(500), nullable=False)
139
140 class Item(TableBase):
141 __tablename__ = 'items'
142 __singlename__ = 'item'
143 id = Column(Integer, primary_key=True, nullable=False)
144 name = Column(Unicode(16), nullable=False)
145
146 class Language(TableBase):
147 __tablename__ = 'languages'
148 id = Column(Integer, primary_key=True, nullable=False)
149 name = Column(Unicode(16), nullable=False)
150
151 class Location(TableBase):
152 __tablename__ = 'locations'
153 __singlename__ = 'location'
154 id = Column(Integer, primary_key=True, nullable=False)
155 generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False)
156 name = Column(Unicode(64), nullable=False)
157
158 class LocationArea(TableBase):
159 __tablename__ = 'location_areas'
160 id = Column(Integer, primary_key=True, nullable=False)
161 location_id = Column(Integer, ForeignKey('locations.id'), nullable=False)
162 internal_id = Column(Integer, nullable=False)
163 name = Column(Unicode(64), nullable=True)
164
165 class LocationAreaEncounterRate(TableBase):
166 __tablename__ = 'location_area_encounter_rates'
167 location_area_id = Column(Integer, ForeignKey('location_areas.id'), primary_key=True, nullable=False, autoincrement=False)
168 encounter_type_id = Column(Integer, ForeignKey('encounter_types.id'), primary_key=True, nullable=False, autoincrement=False)
169 rate = Column(Integer, nullable=True)
170
171 class Machine(TableBase):
172 __tablename__ = 'machines'
173 machine_number = Column(Integer, primary_key=True, nullable=False, autoincrement=False)
174 generation_id = Column(Integer, ForeignKey('generations.id'), primary_key=True, nullable=False, autoincrement=False)
175 move_id = Column(Integer, ForeignKey('moves.id'), nullable=False)
176
177 class MoveDamageClass(TableBase):
178 __tablename__ = 'move_damage_classes'
179 id = Column(Integer, primary_key=True, nullable=False)
180 name = Column(Unicode(8), nullable=False)
181 description = Column(Unicode(64), nullable=False)
182
183 class MoveEffect(TableBase):
184 __tablename__ = 'move_effects'
185 id = Column(Integer, primary_key=True, nullable=False)
186 priority = Column(SmallInteger, nullable=False)
187 short_effect = Column(Unicode(256), nullable=False)
188 effect = Column(Unicode(5120), nullable=False)
189
190 class MoveFlag(TableBase):
191 __tablename__ = 'move_flags'
192 move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False)
193 move_flag_type_id = Column(Integer, ForeignKey('move_flag_types.id'), primary_key=True, nullable=False, autoincrement=False)
194
195 class MoveFlagType(TableBase):
196 __tablename__ = 'move_flag_types'
197 id = Column(Integer, primary_key=True, nullable=False)
198 name = Column(Unicode(32), nullable=False)
199 description = Column(rst.RstTextColumn(128), nullable=False)
200
201 class MoveName(TableBase):
202 __tablename__ = 'move_names'
203 move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False)
204 language_id = Column(Integer, ForeignKey('languages.id'), primary_key=True, nullable=False, autoincrement=False)
205 name = Column(Unicode(16), nullable=False)
206
207 class MoveTarget(TableBase):
208 __tablename__ = 'move_targets'
209 id = Column(Integer, primary_key=True, nullable=False)
210 name = Column(Unicode(32), nullable=False)
211 description = Column(Unicode(128), nullable=False)
212
213 class Move(TableBase):
214 __tablename__ = 'moves'
215 __singlename__ = 'move'
216 id = Column(Integer, primary_key=True, nullable=False)
217 name = Column(Unicode(12), nullable=False)
218 generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False)
219 type_id = Column(Integer, ForeignKey('types.id'), nullable=False)
220 power = Column(SmallInteger)
221 pp = Column(SmallInteger, nullable=False)
222 accuracy = Column(SmallInteger)
223 target_id = Column(Integer, ForeignKey('move_targets.id'), nullable=False)
224 damage_class_id = Column(Integer, ForeignKey('move_damage_classes.id'), nullable=False)
225 effect_id = Column(Integer, ForeignKey('move_effects.id'), nullable=False)
226 effect_chance = Column(Integer)
227 contest_type = Column(Unicode(8), nullable=False)
228 contest_effect_id = Column(Integer, ForeignKey('contest_effects.id'), nullable=True)
229 super_contest_effect_id = Column(Integer, nullable=False)
230
231 class Pokemon(TableBase):
232 """The core to this whole mess.
233
234 Note that I use both 'forme' and 'form' in both code and the database. I
235 only use 'forme' when specifically referring to Pokémon that have multiple
236 distinct species as forms—i.e., different stats or movesets. 'Form' is a
237 more general term referring to any variation within a species, including
238 purely cosmetic forms like Unown.
239 """
240 __tablename__ = 'pokemon'
241 __singlename__ = 'pokemon'
242 id = Column(Integer, primary_key=True, nullable=False)
243 name = Column(Unicode(20), nullable=False)
244 forme_name = Column(Unicode(16))
245 forme_base_pokemon_id = Column(Integer, ForeignKey('pokemon.id'))
246 generation_id = Column(Integer, ForeignKey('generations.id'))
247 evolution_chain_id = Column(Integer, ForeignKey('evolution_chains.id'))
248 evolution_parent_pokemon_id = Column(Integer, ForeignKey('pokemon.id'))
249 evolution_method_id = Column(Integer, ForeignKey('evolution_methods.id'))
250 evolution_parameter = Column(Unicode(32))
251 height = Column(Integer, nullable=False)
252 weight = Column(Integer, nullable=False)
253 species = Column(Unicode(16), nullable=False)
254 color = Column(Unicode(6), nullable=False)
255 pokemon_shape_id = Column(Integer, ForeignKey('pokemon_shapes.id'), nullable=False)
256 habitat = Column(Unicode(16), nullable=False)
257 gender_rate = Column(Integer, nullable=False)
258 capture_rate = Column(Integer, nullable=False)
259 base_experience = Column(Integer, nullable=False)
260 base_happiness = Column(Integer, nullable=False)
261 gen1_internal_id = Column(Integer)
262 is_baby = Column(Boolean, nullable=False)
263 has_gen4_fem_sprite = Column(Boolean, nullable=False)
264 has_gen4_fem_back_sprite = Column(Boolean, nullable=False)
265
266 ### Stuff to handle alternate Pokémon forms
267
268 @property
269 def national_id(self):
270 """Returns the National Pokédex number for this Pokémon. Use this
271 instead of the id directly; alternate formes may make the id incorrect.
272 """
273
274 if self.forme_base_pokemon_id:
275 return self.forme_base_pokemon_id
276 return self.id
277
278 @property
279 def full_name(self):
280 """Returns the name of this Pokémon, including its Forme, if any."""
281
282 if self.forme_name:
283 return "%s %s" % (self.forme_name.capitalize(), self.name)
284 return self.name
285
286 @property
287 def normal_form(self):
288 """Returns the normal form for this Pokémon; i.e., this will return
289 regular Deoxys when called on any Deoxys form.
290 """
291
292 if self.forme_base_pokemon:
293 return self.forme_base_pokemon
294
295 return self
296
297 class PokemonAbility(TableBase):
298 __tablename__ = 'pokemon_abilities'
299 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False)
300 ability_id = Column(Integer, ForeignKey('abilities.id'), nullable=False)
301 slot = Column(Integer, primary_key=True, nullable=False, autoincrement=False)
302
303 class PokemonDexNumber(TableBase):
304 __tablename__ = 'pokemon_dex_numbers'
305 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False)
306 generation_id = Column(Integer, ForeignKey('generations.id'), primary_key=True, nullable=False, autoincrement=False)
307 pokedex_number = Column(Integer, nullable=False)
308
309 class PokemonEggGroup(TableBase):
310 __tablename__ = 'pokemon_egg_groups'
311 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False)
312 egg_group_id = Column(Integer, ForeignKey('egg_groups.id'), primary_key=True, nullable=False, autoincrement=False)
313
314 class PokemonFlavorText(TableBase):
315 __tablename__ = 'pokemon_flavor_text'
316 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False)
317 version_id = Column(Integer, ForeignKey('versions.id'), primary_key=True, nullable=False, autoincrement=False)
318 flavor_text = Column(Unicode(255), nullable=False)
319
320 class PokemonFormGroup(TableBase):
321 __tablename__ = 'pokemon_form_groups'
322 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False)
323 description = Column(Unicode(512), nullable=False)
324
325 class PokemonFormSprite(TableBase):
326 __tablename__ = 'pokemon_form_sprites'
327 id = Column(Integer, primary_key=True, nullable=False)
328 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False)
329 name = Column(Unicode(16), nullable=True)
330
331 class PokemonItem(TableBase):
332 __tablename__ = 'pokemon_items'
333 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False)
334 version_id = Column(Integer, ForeignKey('versions.id'), primary_key=True, nullable=False, autoincrement=False)
335 item_id = Column(Integer, ForeignKey('items.id'), primary_key=True, nullable=False, autoincrement=False)
336 rarity = Column(Integer, nullable=False)
337
338 class PokemonMove(TableBase):
339 __tablename__ = 'pokemon_moves'
340 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False)
341 version_group_id = Column(Integer, ForeignKey('version_groups.id'), primary_key=True, nullable=False, autoincrement=False)
342 move_id = Column(Integer, ForeignKey('moves.id'), primary_key=True, nullable=False, autoincrement=False, index=True)
343 pokemon_move_method_id = Column(Integer, ForeignKey('pokemon_move_methods.id'), primary_key=True, nullable=False, autoincrement=False)
344 level = Column(Integer, primary_key=True, nullable=True, autoincrement=False)
345 order = Column(Integer, nullable=True)
346
347 class PokemonMoveMethod(TableBase):
348 __tablename__ = 'pokemon_move_methods'
349 id = Column(Integer, primary_key=True, nullable=False, autoincrement=False)
350 name = Column(Unicode(64), nullable=False)
351 description = Column(Unicode(255), nullable=False)
352
353 class PokemonName(TableBase):
354 __tablename__ = 'pokemon_names'
355 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False)
356 language_id = Column(Integer, ForeignKey('languages.id'), primary_key=True, nullable=False, autoincrement=False)
357 name = Column(Unicode(16), nullable=False)
358
359 class PokemonShape(TableBase):
360 __tablename__ = 'pokemon_shapes'
361 id = Column(Integer, primary_key=True, nullable=False)
362 name = Column(Unicode(24), nullable=False)
363 awesome_name = Column(Unicode(16), nullable=False)
364
365 class PokemonStat(TableBase):
366 __tablename__ = 'pokemon_stats'
367 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False)
368 stat_id = Column(Integer, ForeignKey('stats.id'), primary_key=True, nullable=False, autoincrement=False)
369 base_stat = Column(Integer, nullable=False)
370 effort = Column(Integer, nullable=False)
371
372 class PokemonType(TableBase):
373 __tablename__ = 'pokemon_types'
374 pokemon_id = Column(Integer, ForeignKey('pokemon.id'), primary_key=True, nullable=False, autoincrement=False)
375 type_id = Column(Integer, ForeignKey('types.id'), nullable=False)
376 slot = Column(Integer, primary_key=True, nullable=False, autoincrement=False)
377
378 class Stat(TableBase):
379 __tablename__ = 'stats'
380 id = Column(Integer, primary_key=True, nullable=False)
381 name = Column(Unicode(16), nullable=False)
382
383 class TypeEfficacy(TableBase):
384 __tablename__ = 'type_efficacy'
385 damage_type_id = Column(Integer, ForeignKey('types.id'), primary_key=True, nullable=False, autoincrement=False)
386 target_type_id = Column(Integer, ForeignKey('types.id'), primary_key=True, nullable=False, autoincrement=False)
387 damage_factor = Column(Integer, nullable=False)
388
389 class Type(TableBase):
390 __tablename__ = 'types'
391 __singlename__ = 'type'
392 id = Column(Integer, primary_key=True, nullable=False)
393 name = Column(Unicode(8), nullable=False)
394 abbreviation = Column(Unicode(3), nullable=False)
395
396 class VersionGroup(TableBase):
397 __tablename__ = 'version_groups'
398 id = Column(Integer, primary_key=True, nullable=False)
399 generation_id = Column(Integer, ForeignKey('generations.id'), nullable=False)
400
401 class Version(TableBase):
402 __tablename__ = 'versions'
403 id = Column(Integer, primary_key=True, nullable=False)
404 version_group_id = Column(Integer, ForeignKey('version_groups.id'), nullable=False)
405 name = Column(Unicode(32), nullable=False)
406
407
408 ### Relations down here, to avoid ordering problems
409 Encounter.pokemon = relation(Pokemon, backref='encounters')
410 Encounter.version = relation(Version, backref='encounters')
411 Encounter.location_area = relation(LocationArea, backref='encounters')
412 Encounter.slot = relation(EncounterTypeSlot, backref='encounters')
413 Encounter.condition = relation(EncounterCondition, backref='encounters')
414
415 EncounterCondition.group = relation(EncounterConditionGroup,
416 backref='conditions')
417
418 EncounterTypeSlot.type = relation(EncounterType, backref='slots')
419
420 EvolutionChain.growth_rate = relation(GrowthRate, backref='evolution_chains')
421
422 Generation.versions = relation(Version, secondary=VersionGroup.__table__)
423
424 LocationArea.location = relation(Location, backref='areas')
425
426 Machine.generation = relation(Generation)
427
428 Move.damage_class = relation(MoveDamageClass, backref='moves')
429 Move.flags = association_proxy('move_flags', 'flag')
430 Move.foreign_names = relation(MoveName, backref='pokemon')
431 Move.generation = relation(Generation, backref='moves')
432 Move.machines = relation(Machine, backref='move')
433 Move.move_effect = relation(MoveEffect, backref='moves')
434 Move.move_flags = relation(MoveFlag, backref='move')
435 Move.target = relation(MoveTarget, backref='moves')
436 Move.type = relation(Type, backref='moves')
437
438 Move.effect = rst.MoveEffectProperty('effect')
439 Move.priority = association_proxy('move_effect', 'priority')
440 Move.short_effect = rst.MoveEffectProperty('short_effect')
441
442 MoveFlag.flag = relation(MoveFlagType)
443
444 MoveName.language = relation(Language)
445
446 Pokemon.abilities = relation(Ability, secondary=PokemonAbility.__table__,
447 order_by=PokemonAbility.slot,
448 backref='pokemon')
449 Pokemon.formes = relation(Pokemon, primaryjoin=Pokemon.id==Pokemon.forme_base_pokemon_id,
450 backref=backref('forme_base_pokemon',
451 remote_side=[Pokemon.id]))
452 Pokemon.dex_numbers = relation(PokemonDexNumber, backref='pokemon')
453 Pokemon.egg_groups = relation(EggGroup, secondary=PokemonEggGroup.__table__,
454 order_by=PokemonEggGroup.egg_group_id,
455 backref='pokemon')
456 Pokemon.evolution_chain = relation(EvolutionChain, backref='pokemon')
457 Pokemon.evolution_method = relation(EvolutionMethod)
458 Pokemon.evolution_children = relation(Pokemon, primaryjoin=Pokemon.id==Pokemon.evolution_parent_pokemon_id,
459 backref=backref('evolution_parent',
460 remote_side=[Pokemon.id]))
461 Pokemon.flavor_text = relation(PokemonFlavorText, backref='pokemon')
462 Pokemon.foreign_names = relation(PokemonName, backref='pokemon')
463 Pokemon.items = relation(PokemonItem)
464 Pokemon.generation = relation(Generation, backref='pokemon')
465 Pokemon.shape = relation(PokemonShape, backref='pokemon')
466 Pokemon.stats = relation(PokemonStat, backref='pokemon')
467 Pokemon.types = relation(Type, secondary=PokemonType.__table__)
468
469 PokemonDexNumber.generation = relation(Generation)
470
471 PokemonFlavorText.version = relation(Version)
472
473 PokemonItem.item = relation(Item, backref='pokemon')
474 PokemonItem.version = relation(Version)
475
476 PokemonFormGroup.pokemon = relation(Pokemon, backref=backref('form_group',
477 uselist=False))
478 PokemonFormSprite.pokemon = relation(Pokemon, backref='form_sprites')
479
480 PokemonMove.pokemon = relation(Pokemon, backref='pokemon_moves')
481 PokemonMove.version_group = relation(VersionGroup)
482 PokemonMove.move = relation(Move, backref='pokemon_moves')
483 PokemonMove.method = relation(PokemonMoveMethod)
484
485 PokemonName.language = relation(Language)
486
487 PokemonStat.stat = relation(Stat)
488
489 Type.damage_efficacies = relation(TypeEfficacy,
490 primaryjoin=Type.id
491 ==TypeEfficacy.damage_type_id,
492 backref='damage_type')
493 Type.target_efficacies = relation(TypeEfficacy,
494 primaryjoin=Type.id
495 ==TypeEfficacy.target_type_id,
496 backref='target_type')
497
498 Version.version_group = relation(VersionGroup, backref='versions')
499 Version.generation = association_proxy('version_group', 'generation')
500
501 VersionGroup.generation = relation(Generation, backref='version_groups')