Changeset 511

Show
Ignore:
Timestamp:
01/26/10 14:18:05 (6 weeks ago)
Author:
olivier
Message:

Decorate Django fields so that they are not required (blank=True), and also have a default=value by default. The goal is DRY in the CREM models, and especially to prepare adding verbose names without cluttering the model classes.

Location:
trunk/telemeta/models
Files:
2 modified

Legend:

Unmodified
Added
Removed
  • trunk/telemeta/models/core.py

    r508 r511  
    9393        return self._delta.days * 24 * 3600 + self._delta.seconds 
    9494             
     95def normalize_field(args, default_value): 
     96    """Normalize field constructor arguments, so that the field is marked blank=True 
     97       and has a default value by default. 
     98        
     99       This behaviour can be disabled by passing the special argument required=True. 
     100 
     101       The default value can also be overriden with the default=value argument. 
     102       """ 
     103    required = False 
     104    if args.has_key('required'): 
     105        required = args['required'] 
     106        args.pop('required') 
     107 
     108    args['blank'] = not required 
     109 
     110    if not required: 
     111        if not args.has_key('default'): 
     112            if args.get('null'): 
     113                args['default'] = None 
     114            else: 
     115                args['default'] = default_value 
     116 
     117    return args                 
     118 
    95119# The following is based on Django TimeField 
    96120class DurationField(models.Field): 
    97121    """Duration Django model field. Essentially the same as a TimeField, but 
    98     with values over 24h allowed.""" 
     122    with values over 24h allowed. 
     123     
     124    The constructor arguments are also normalized with normalize_field().  
     125    """ 
    99126 
    100127    description = _("Duration") 
     
    105132        'invalid': _('Enter a valid duration in HH:MM[:ss[.uuuuuu]] format.'), 
    106133    } 
     134 
     135    def __init__(self, *args, **kwargs): 
     136        super(DurationField, self).__init__(args, **normalize_field(kwargs, '00:00')) 
    107137 
    108138    def get_internal_type(self): 
     
    145175        return super(DurationField, self).formfield(**defaults) 
    146176            
    147  
    148 class WeakForeignKey(models.ForeignKey): 
     177class ForeignKey(models.ForeignKey): 
     178    """The constructor arguments of this ForeignKey are normalized 
     179    with normalize_field(), however the field is marked required by default 
     180    unless it is allowed to be null.""" 
     181 
     182    def __init__(self, to, **kwargs): 
     183        if not kwargs.has_key('required'): 
     184            if not kwargs.get('null'): 
     185                kwargs['required'] = True 
     186 
     187        super(ForeignKey, self).__init__(to, **normalize_field(kwargs, 0))                 
     188 
     189class WeakForeignKey(ForeignKey): 
    149190    """A weak foreign key is the same as foreign key but without cascading 
    150191    delete. Instead the reference is set to null when the referenced record 
    151192    get deleted. This emulates the ON DELETE SET NULL sql behaviour. 
    152      
     193    
     194    This field is automatically allowed to be null, there's no need to pass 
     195    null=True.  
     196 
     197    The constructor arguments are normalized with normalize_field() by the 
     198    parent ForeignKey 
     199 
    153200    Warning: must be used in conjunction with EnhancedQuerySet, EnhancedManager, 
    154201    and EnhancedModel 
    155202    """ 
    156203    def __init__(self, to, **kwargs): 
     204        kwargs['null'] = True 
    157205        super(WeakForeignKey, self).__init__(to, **kwargs) 
    158206        
     
    198246    class Meta: 
    199247        abstract = True 
     248 
     249class CharField(models.CharField): 
     250    """This is a CharField with a default max_length of 250. 
     251     
     252       The arguments are also normalized with normalize_field()""" 
     253 
     254    def __init__(self, *args, **kwargs): 
     255        if not kwargs.has_key('max_length'): 
     256            kwargs['max_length'] = 250 
     257 
     258        super(CharField, self).__init__(*args, **normalize_field(kwargs, '')) 
     259 
     260class IntegerField(models.IntegerField): 
     261    """IntegerField normalized with normalize_field()""" 
     262 
     263    def __init__(self, *args, **kwargs): 
     264        super(IntegerField, self).__init__(*args, **normalize_field(kwargs, 0)) 
     265 
     266class BooleanField(models.BooleanField): 
     267    """BooleanField normalized with normalize_field()""" 
     268 
     269    def __init__(self, *args, **kwargs): 
     270        super(BooleanField, self).__init__(*args, **normalize_field(kwargs, False)) 
     271 
     272class TextField(models.TextField): 
     273    """TextField normalized with normalize_field()""" 
     274 
     275    def __init__(self, *args, **kwargs): 
     276        super(TextField, self).__init__(*args, **normalize_field(kwargs, '')) 
     277 
     278class DateTimeField(models.DateTimeField): 
     279    """DateTimeField normalized with normalize_field()""" 
     280 
     281    def __init__(self, *args, **kwargs): 
     282        super(DateTimeField, self).__init__(*args, **normalize_field(kwargs, '0000-00-00 00:00')) 
     283 
     284class FileField(models.FileField): 
     285    """FileField normalized with normalize_field()""" 
     286 
     287    def __init__(self, *args, **kwargs): 
     288        super(FileField, self).__init__(*args, **normalize_field(kwargs, '')) 
     289 
     290class FloatField(models.FloatField): 
     291    """FloatField normalized with normalize_field()""" 
     292 
     293    def __init__(self, *args, **kwargs): 
     294        super(FloatField, self).__init__(*args, **normalize_field(kwargs, 0)) 
     295 
     296class DateField(models.DateField): 
     297    """DateField normalized with normalize_field()""" 
     298 
     299    def __init__(self, *args, **kwargs): 
     300        super(DateField, self).__init__(*args, **normalize_field(kwargs, '0000-00-00')) 
     301 
  • trunk/telemeta/models/crem.py

    r510 r511  
    3434#          David LIPSZYC <davidlipszyc@gmail.com> 
    3535 
    36 from django.db import models 
    3736from django.core.exceptions import ObjectDoesNotExist 
    3837import cremquery as query 
     
    4039from telemeta.util.unaccent import unaccent_icmp 
    4140import re 
    42 from telemeta.models.core import DurationField, Duration, WeakForeignKey, EnhancedModel 
     41from telemeta.models.core import DurationField, Duration, WeakForeignKey, EnhancedModel, \ 
     42                                 CharField, TextField, IntegerField, BooleanField, \ 
     43                                 DateTimeField, FileField, ForeignKey, FloatField, DateField 
    4344from telemeta.models import dublincore as dc 
    4445 
     
    4950        required = [] 
    5051        for field in cls._meta.fields: 
    51             if (field != cls._meta.pk and field.default == models.fields.NOT_PROVIDED and 
    52                     not field.null and not getattr(field, 'auto_now_add', False)): 
     52            if not field.blank: 
    5353                required.append(field) 
    5454        return required 
     
    9494        for name, value in fields.iteritems(): 
    9595            element = doc.createElement(self.get_dom_field_name(name)) 
    96             if isinstance(value, models.Model): 
     96            if isinstance(value, EnhancedModel): 
    9797                element.setAttribute('key', str(value.pk)) 
    9898            value = unicode(value) 
     
    136136    code_regex             = '(?:%s|%s)' % (published_code_regex, unpublished_code_regex) 
    137137 
    138     reference             = models.CharField(unique=True, max_length=250, 
    139                                              null=True) 
    140     physical_format       = WeakForeignKey('PhysicalFormat', related_name="collections", null=True) 
    141     old_code              = models.CharField(unique=True, max_length=250, null=True) 
    142     code                  = models.CharField(unique=True, max_length=250) 
    143     title                 = models.CharField(max_length=250) 
    144     alt_title             = models.CharField(max_length=250, default="") 
    145     physical_items_num    = models.IntegerField(default=0) 
    146     publishing_status     = WeakForeignKey('PublishingStatus', related_name="collections", null=True) 
    147     creator               = models.CharField(max_length=250, default="") 
    148     booklet_author        = models.CharField(max_length=250, default="") 
    149     booklet_description   = models.TextField(default="") 
    150     collector             = models.CharField(max_length=250, default="") 
    151     collector_is_creator  = models.BooleanField(default="") 
    152     publisher             = WeakForeignKey('Publisher', related_name="collections", null=True)      
    153     is_published          = models.BooleanField(default="") 
    154     year_published        = models.IntegerField(default=0) 
    155     publisher_collection  = WeakForeignKey('PublisherCollection', related_name="collections", null=True) 
    156     publisher_serial      = models.CharField(max_length=250, default="") 
    157     external_references   = models.TextField(default="") 
    158     acquisition_mode      = WeakForeignKey('AcquisitionMode', related_name="collections", null=True) 
    159     comment               = models.TextField(default="") 
    160     metadata_author       = WeakForeignKey('MetadataAuthor', related_name="collections", null=True) 
    161     metadata_writer       = WeakForeignKey('MetadataWriter', related_name="collections", null=True) 
    162     legal_rights          = WeakForeignKey('LegalRight', related_name="collections", null=True) 
    163     alt_ids               = models.CharField(max_length=250, default="") 
    164     recorded_from_year    = models.IntegerField(default=0) 
    165     recorded_to_year      = models.IntegerField(default=0) 
    166     recording_context     = WeakForeignKey('RecordingContext', related_name="collections", null=True) 
    167     approx_duration       = DurationField(default='00:00') 
    168     doctype_code          = models.IntegerField(default=0) 
    169     travail               = models.CharField(max_length=250, default="") 
    170     state                 = models.TextField(default="") 
    171     cnrs_contributor      = models.CharField(max_length=250, default="") 
    172     items_done            = models.CharField(max_length=250, default="") 
    173     a_informer_07_03      = models.CharField(max_length=250, default="") 
    174     ad_conversion         = WeakForeignKey('AdConversion', related_name='collections', null=True) 
    175     public_access         = models.CharField(choices=PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata") 
     138    reference             = CharField(unique=True, null=True) 
     139    physical_format       = WeakForeignKey('PhysicalFormat', related_name="collections") 
     140    old_code              = CharField(unique=True, null=True) 
     141    code                  = CharField(unique=True, required=True) 
     142    title                 = CharField(required=True) 
     143    alt_title             = CharField() 
     144    physical_items_num    = IntegerField(default=0) 
     145    publishing_status     = WeakForeignKey('PublishingStatus', related_name="collections") 
     146    creator               = CharField() 
     147    booklet_author        = CharField() 
     148    booklet_description   = TextField() 
     149    collector             = CharField() 
     150    collector_is_creator  = BooleanField() 
     151    publisher             = WeakForeignKey('Publisher', related_name="collections")      
     152    is_published          = BooleanField() 
     153    year_published        = IntegerField() 
     154    publisher_collection  = WeakForeignKey('PublisherCollection', related_name="collections") 
     155    publisher_serial      = CharField() 
     156    external_references   = TextField() 
     157    acquisition_mode      = WeakForeignKey('AcquisitionMode', related_name="collections") 
     158    comment               = TextField() 
     159    metadata_author       = WeakForeignKey('MetadataAuthor', related_name="collections") 
     160    metadata_writer       = WeakForeignKey('MetadataWriter', related_name="collections") 
     161    legal_rights          = WeakForeignKey('LegalRight', related_name="collections") 
     162    alt_ids               = CharField() 
     163    recorded_from_year    = IntegerField() 
     164    recorded_to_year      = IntegerField() 
     165    recording_context     = WeakForeignKey('RecordingContext', related_name="collections") 
     166    approx_duration       = DurationField() 
     167    doctype_code          = IntegerField() 
     168    travail               = CharField() 
     169    state                 = TextField() 
     170    cnrs_contributor      = CharField() 
     171    items_done            = CharField() 
     172    a_informer_07_03      = CharField() 
     173    ad_conversion         = WeakForeignKey('AdConversion', related_name='collections') 
     174    public_access         = CharField(choices=PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata") 
    176175 
    177176    objects               = query.MediaCollectionManager() 
     
    252251    code_regex              = '(?:%s|%s)' % (published_code_regex, unpublished_code_regex) 
    253252 
    254     collection            = models.ForeignKey('MediaCollection', related_name="items") 
    255     track                 = models.CharField(max_length=250, default="") 
    256     old_code              = models.CharField(unique=True, max_length=250, null=True) 
    257     code                  = models.CharField(unique=True, max_length=250, null=True) 
    258     approx_duration       = DurationField(default='00:00') 
    259     recorded_from_date    = models.DateField(default=0) 
    260     recorded_to_date      = models.DateField(default=0) 
     253    collection            = ForeignKey('MediaCollection', related_name="items") 
     254    track                 = CharField() 
     255    old_code              = CharField(unique=True, null=True) 
     256    code                  = CharField(unique=True, null=True) 
     257    approx_duration       = DurationField() 
     258    recorded_from_date    = DateField() 
     259    recorded_to_date      = DateField() 
    261260    location              = WeakForeignKey('Location', related_name="items", 
    262                                            db_column='location_name', null=True) 
    263     location_comment      = models.CharField(max_length=250, default="") 
    264     ethnic_group          = WeakForeignKey('EthnicGroup', related_name="items", null=True) 
    265     title                 = models.CharField(max_length=250) 
    266     alt_title             = models.CharField(max_length=250, default="") 
    267     author                = models.CharField(max_length=250, default="") 
    268     vernacular_style      = WeakForeignKey('VernacularStyle', related_name="items", 
    269                                               null=True) 
    270     context_comment       = models.TextField(default="") 
    271     external_references   = models.TextField(default="") 
    272     moda_execut           = models.CharField(max_length=250, default="") 
    273     copied_from_item      = WeakForeignKey('self', related_name="copies", null=True) 
    274     collector             = models.CharField(max_length=250, default="") 
    275     cultural_area         = models.CharField(max_length=250, default="") 
    276     generic_style         = WeakForeignKey('GenericStyle', related_name="items", 
    277                                               null=True) 
    278     collector_selection   = models.CharField(max_length=250, default="") 
    279     creator_reference     = models.CharField(max_length=250, default="") 
    280     comment               = models.TextField(default="") 
    281     file                  = models.FileField(upload_to='items/%Y/%m/%d', db_column="filename", default='') 
    282     public_access         = models.CharField(choices=PUBLIC_ACCESS_CHOICES,  
    283                                              max_length=16, default="metadata") 
     261                                           db_column='location_name') 
     262    location_comment      = CharField() 
     263    ethnic_group          = WeakForeignKey('EthnicGroup', related_name="items") 
     264    title                 = CharField(required=True) 
     265    alt_title             = CharField() 
     266    author                = CharField() 
     267    vernacular_style      = WeakForeignKey('VernacularStyle', related_name="items") 
     268    context_comment       = TextField() 
     269    external_references   = TextField() 
     270    moda_execut           = CharField() 
     271    copied_from_item      = WeakForeignKey('self', related_name="copies") 
     272    collector             = CharField() 
     273    cultural_area         = CharField() 
     274    generic_style         = WeakForeignKey('GenericStyle', related_name="items") 
     275    collector_selection   = CharField() 
     276    creator_reference     = CharField() 
     277    comment               = TextField() 
     278    file                  = FileField(upload_to='items/%Y/%m/%d', db_column="filename") 
     279    public_access         = CharField(choices=PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata") 
    284280 
    285281    objects               = query.MediaItemManager() 
     
    344340    "Describe an item part" 
    345341    element_type = 'part' 
    346     item  = models.ForeignKey('MediaItem', related_name="parts") 
    347     title = models.CharField(max_length=250) 
    348     start = models.FloatField() 
    349     end   = models.FloatField() 
     342    item  = ForeignKey('MediaItem', related_name="parts") 
     343    title = CharField(required=True) 
     344    start = FloatField(required=True) 
     345    end   = FloatField(required=True) 
    350346     
    351347    class Meta(MetaCore): 
     
    357353class Enumeration(ModelCore): 
    358354    "Abstract enumerations base class" 
    359     value = models.CharField(max_length=250, unique=True) 
     355    value = CharField(required=True, unique=True) 
    360356     
    361357    def __unicode__(self): 
     
    427423class Instrument(ModelCore): 
    428424    "Instrument used in the item" 
    429     name    = models.CharField(max_length=250) 
     425    name    = CharField(required=True) 
    430426 
    431427    class Meta(MetaCore): 
     
    437433class InstrumentAlias(ModelCore): 
    438434    "Instrument other name" 
    439     name = models.CharField(max_length=250) 
     435    name = CharField(required=True) 
    440436 
    441437    class Meta(MetaCore): 
     
    447443class InstrumentRelation(ModelCore): 
    448444    "Instrument family" 
    449     instrument        = models.ForeignKey('Instrument', related_name="parent_relation") 
    450     parent_instrument = models.ForeignKey('Instrument', related_name="child_relation") 
     445    instrument        = ForeignKey('Instrument', related_name="parent_relation") 
     446    parent_instrument = ForeignKey('Instrument', related_name="child_relation") 
    451447 
    452448    class Meta(MetaCore): 
     
    456452class InstrumentAliasRelation(ModelCore): 
    457453    "Instrument family other name" 
    458     alias      = models.ForeignKey('InstrumentAlias', related_name="other_name") 
    459     instrument = models.ForeignKey('InstrumentAlias', related_name="relation") 
     454    alias      = ForeignKey('InstrumentAlias', related_name="other_name") 
     455    instrument = ForeignKey('InstrumentAlias', related_name="relation") 
    460456 
    461457    class Meta(MetaCore): 
     
    465461class MediaItemPerformance(ModelCore): 
    466462    "Item performance" 
    467     media_item      = models.ForeignKey('MediaItem', related_name="performances") 
    468     instrument      = WeakForeignKey('Instrument', related_name="performances", null=True) 
    469     alias           = WeakForeignKey('InstrumentAlias', related_name="performances", null=True) 
    470     instruments_num = models.CharField(max_length=250, default="") 
    471     musicians       = models.CharField(max_length=250, default="") 
     463    media_item      = ForeignKey('MediaItem', related_name="performances") 
     464    instrument      = WeakForeignKey('Instrument', related_name="performances") 
     465    alias           = WeakForeignKey('InstrumentAlias', related_name="performances") 
     466    instruments_num = CharField() 
     467    musicians       = CharField() 
    472468 
    473469    class Meta(MetaCore): 
     
    478474    LEVEL_CHOICES = (('user', 'user'), ('maintainer', 'maintainer'), ('admin', 'admin'))     
    479475 
    480     username   = models.CharField(primary_key=True, max_length=64) 
    481     level      = models.CharField(choices=LEVEL_CHOICES, max_length=250) 
    482     first_name = models.CharField(max_length=250, default="") 
    483     last_name  = models.CharField(max_length=250, default="") 
    484     phone      = models.CharField(max_length=250, default="") 
    485     email      = models.CharField(max_length=250, default="") 
     476    username   = CharField(primary_key=True, max_length=64, required=True) 
     477    level      = CharField(choices=LEVEL_CHOICES, max_length=32, required=True) 
     478    first_name = CharField() 
     479    last_name  = CharField() 
     480    phone      = CharField() 
     481    email      = CharField() 
    486482 
    487483    class Meta(MetaCore): 
     
    493489class Playlist(ModelCore): 
    494490    "Item or collection playlist" 
    495     owner_username = models.ForeignKey('User', related_name="playlists", db_column="owner_username")  
    496     name           = models.CharField(max_length=250) 
     491    owner_username = ForeignKey('User', related_name="playlists", db_column="owner_username")  
     492    name           = CharField(required=True) 
    497493 
    498494    class Meta(MetaCore): 
     
    506502    RESOURCE_TYPE_CHOICES = (('item', 'item'), ('collection', 'collection')) 
    507503 
    508     playlist              = models.ForeignKey('Playlist', related_name="resources") 
    509     resource_type         = models.CharField(choices=RESOURCE_TYPE_CHOICES, max_length=250) 
    510     resource              = models.IntegerField() 
     504    playlist              = ForeignKey('Playlist', related_name="resources") 
     505    resource_type         = CharField(choices=RESOURCE_TYPE_CHOICES, required=True) 
     506    resource              = IntegerField(required=True) 
    511507 
    512508    class Meta(MetaCore): 
     
    517513    TYPE_CHOICES     = (('country', 'country'), ('continent', 'continent'), ('other', 'other')) 
    518514 
    519     name             = models.CharField(primary_key=True, max_length=150) 
    520     type             = models.CharField(choices=TYPE_CHOICES, max_length=16) 
    521     complete_type    = models.ForeignKey('LocationType', related_name="types") 
     515    name             = CharField(primary_key=True, max_length=150, required=True) 
     516    type             = CharField(choices=TYPE_CHOICES, max_length=16, required=True) 
     517    complete_type    = ForeignKey('LocationType', related_name="types") 
    522518    current_name     = WeakForeignKey('self', related_name="past_names",  
    523                                       db_column="current_name", null=True)  
    524     is_authoritative = models.BooleanField(default=0) 
     519                                      db_column="current_name")  
     520    is_authoritative = BooleanField() 
    525521 
    526522    def parent(self): 
     
    564560class LocationType(ModelCore): 
    565561    "Location type of an item location" 
    566     id   = models.CharField(max_length=64, primary_key=True) 
    567     name = models.CharField(max_length=150) 
     562    id   = CharField(max_length=64, primary_key=True, required=True) 
     563    name = CharField(max_length=150, required=True) 
    568564 
    569565    class Meta(MetaCore): 
     
    572568class LocationAlias(ModelCore): 
    573569    "Location other name" 
    574     location         = models.ForeignKey('Location', related_name="aliases", 
     570    location         = ForeignKey('Location', related_name="aliases", 
    575571                                          db_column="location_name", max_length=150) 
    576     alias            = models.CharField(max_length=150) 
    577     is_authoritative = models.BooleanField(default=0) 
     572    alias            = CharField(max_length=150, required=True) 
     573    is_authoritative = BooleanField() 
    578574 
    579575    def __unicode__(self): 
     
    586582class LocationRelation(ModelCore): 
    587583    "Location family" 
    588     location             = models.ForeignKey('Location', related_name="parent_relations", 
     584    location             = ForeignKey('Location', related_name="parent_relations", 
    589585                                              db_column="location_name", max_length=150) 
    590     parent_location      = models.ForeignKey('Location', related_name="child_relations", 
     586    parent_location      = ForeignKey('Location', related_name="child_relations", 
    591587                                              db_column="parent_location_name", null=True, max_length=150) 
    592     is_authoritative     = models.BooleanField() 
     588    is_authoritative     = BooleanField() 
    593589 
    594590    class Meta(MetaCore): 
     
    603599class MediaItemKeyword(ModelCore): 
    604600    "Item keyword" 
    605     item    = models.ForeignKey('MediaItem') 
    606     keyword = models.ForeignKey('ContextKeyword') 
     601    item    = ForeignKey('MediaItem') 
     602    keyword = ForeignKey('ContextKeyword') 
    607603 
    608604    class Meta(MetaCore): 
     
    618614class PublisherCollection(ModelCore): 
    619615    "Collection which belongs to publisher" 
    620     publisher = models.ForeignKey('Publisher', related_name="publisher_collections") 
    621     value     = models.CharField(max_length=250) 
     616    publisher = ForeignKey('Publisher', related_name="publisher_collections") 
     617    value     = CharField(required=True) 
    622618 
    623619    def __unicode__(self): 
     
    632628    CHANGE_TYPE_CHOICES  = (('import', 'import'), ('create', 'create'), ('update', 'update'), ('delete','delete')) 
    633629 
    634     element_type         = models.CharField(choices=ELEMENT_TYPE_CHOICES, max_length=16) 
    635     element_id           = models.IntegerField() 
    636     change_type          = models.CharField(choices=CHANGE_TYPE_CHOICES, max_length=16) 
    637     time                 = models.DateTimeField(auto_now_add=True) 
    638     user                 = models.ForeignKey('User', db_column='username', related_name="revisions") 
     630    element_type         = CharField(choices=ELEMENT_TYPE_CHOICES, max_length=16, required=True) 
     631    element_id           = IntegerField(required=True) 
     632    change_type          = CharField(choices=CHANGE_TYPE_CHOICES, max_length=16, required=True) 
     633    time                 = DateTimeField(auto_now_add=True) 
     634    user                 = ForeignKey('User', db_column='username', related_name="revisions") 
    639635     
    640636    @classmethod 
     
    659655class EthnicGroup(ModelCore): 
    660656    "Item ethnic group" 
    661     name = models.CharField(max_length=250) 
     657    name = CharField(required=True) 
    662658 
    663659    class Meta(MetaCore): 
     
    669665class EthnicGroupAlias(ModelCore): 
    670666    "Item ethnic group other name"  
    671     ethnic_group = models.ForeignKey('EthnicGroup', related_name="aliases") 
    672     name         = models.CharField(max_length=250) 
     667    ethnic_group = ForeignKey('EthnicGroup', related_name="aliases") 
     668    name         = CharField(required=True) 
    673669 
    674670    class Meta(MetaCore):