class CharSetModel(Model): field = SetCharField( base_field=CharField(max_length=8), size=3, max_length=32, ) field2 = SetCharField(base_field=CharField(max_length=8), max_length=255)
def test_max_length(self): field = SetCharField( models.CharField(max_length=32), size=3, max_length=32 ) field.clean({'a', 'b', 'c'}, None) with pytest.raises(exceptions.ValidationError) as excinfo: field.clean({'a', 'b', 'c', 'd'}, None) assert ( excinfo.value.messages[0] == 'Set contains 4 items, it should contain no more than 3.' )
class CharSetDefaultModel(Model): field = SetCharField( base_field=CharField(max_length=5), size=5, max_length=32, default=lambda: {"a", "d"}, )
class Migration(migrations.Migration): dependencies = [] operations = [ migrations.CreateModel( name="IntSetDefaultModel", fields=[ ( "id", models.AutoField( verbose_name="ID", serialize=False, auto_created=True, primary_key=True, ), ), ( "field", SetCharField(models.IntegerField(), size=None, max_length=32), ), ], options={}, bases=(models.Model,), ) ]
class Migration(migrations.Migration): dependencies = [("testapp", "0001_initial")] operations = [ migrations.AddField( model_name="intsetdefaultmodel", name="field_2", field=SetCharField( models.IntegerField(), default=lambda: set(), size=None, max_length=32 ), ), migrations.AddField( model_name="intsetdefaultmodel", name="field_3", field=SetCharField( models.IntegerField(), default=lambda: {1, 5}, size=None, max_length=32 ), ), ]
class CardToken(models.Model): COST_PATTERN = r"(({(h[wubrg]|\u221E|\u00BD|(([0-9]+|[WUBRGCXYZST])(\/[WUBRGP])?))})|\(.*\))" name = models.CharField(max_length=255) colors = SetCharField(base_field=models.CharField(max_length=50), max_length=(5 * 51), null=True) type = models.CharField(max_length=255) text = models.TextField(null=True) power = models.CharField(max_length=255, null=True) toughness = models.CharField(max_length=255, null=True) @property def html_text(self): return self.transform_tags(self.text) def transform_tags(self, string): if (string == None or string == ''): return '' complete = '' empty = '' regex = re.compile(self.COST_PATTERN) last_index = 0 string = "<br />".join(string.split('\n')) for tag in regex.finditer(string): if (tag.group()[0] == '{'): raw = tag.group()[1:-1].lower() bool_split = raw.find('/') != -1 and raw.find('p') == -1 split = ' ms-split' if bool_split else '' bool_half = raw[0] == 'h' if bool_half: # Scott says this is bad, fairly certain it isn't raw = raw[1:] raw = empty.join(raw.split('/')) if raw == 't': raw += 'ap' elif raw == "\u221E": raw = 'infinity' elif raw == "\u00BD": raw = 'half' # The previous 6 lines might be able to be cleaned up html_tag = "<i class=\"ms ms-cost ms-shadow ms-%s%s\"></i>" % ( raw, split) if bool_half: html_tag = '<span class="ms-half">' + html_tag + '</span>' else: html_tag = '<span class="reminder">%s</span>' % tag.group() complete += string[last_index:tag.start()] + html_tag last_index = tag.end() complete += string[last_index:] return complete
class Migration(migrations.Migration): dependencies = [ ('testapp', '0001_initial'), ] operations = [ migrations.AddField( model_name='intsetdefaultmodel', name='field_2', field=SetCharField(models.IntegerField(), default=lambda: set(), size=None, max_length=32), ), migrations.AddField( model_name='intsetdefaultmodel', name='field_3', field=SetCharField(models.IntegerField(), default=lambda: {1, 5}, size=None, max_length=32), ), ]
def test_makemigrations(self): field = SetCharField(models.CharField(max_length=5), max_length=32) statement, imports = MigrationWriter.serialize(field) # The order of the output max_length/size statements varies by # python version, hence a little regexp to match them assert re.compile( r"""^django_mysql\.models\.SetCharField\( models\.CharField\(max_length=5\),\ # space here ( max_length=32,\ size=None| size=None,\ max_length=32 ) \)$ """, re.VERBOSE).match(statement)
def test_max_length(self): field = SetCharField(models.CharField(max_length=32), size=3, max_length=32) field.clean({"a", "b", "c"}, None) with pytest.raises(exceptions.ValidationError) as excinfo: field.clean({"a", "b", "c", "d"}, None) assert ( excinfo.value.messages[0] == "Set contains 4 items, it should contain no more than 3." )
def test_max_length(self): field = SetCharField( models.CharField(max_length=32), size=3, max_length=32, ) field.clean({'a', 'b', 'c'}, None) with pytest.raises(exceptions.ValidationError) as excinfo: field.clean({'a', 'b', 'c', 'd'}, None) assert excinfo.value.messages[ 0] == 'Set contains 4 items, it should contain no more than 3.'
class InvalidSetCharFieldModel4(TemporaryModel): field = SetCharField(models.CharField(max_length=2), size=2)
def test_deconstruct(self): field = SetCharField(models.IntegerField(), max_length=32) name, path, args, kwargs = field.deconstruct() new = SetCharField(*args, **kwargs) assert new.base_field.__class__ == field.base_field.__class__
class InvalidSetCharFieldModel1(TemporaryModel): field = SetCharField(models.CharField(), max_length=32)
class InvalidSetCharFieldModel2(TemporaryModel): field = SetCharField( models.ForeignKey("testapp.Author", on_delete=models.CASCADE), max_length=32, )
def test_model_field_formfield(self): model_field = SetCharField(models.CharField(max_length=27)) form_field = model_field.formfield() assert isinstance(form_field, SimpleSetField) assert isinstance(form_field.base_field, forms.CharField) assert form_field.base_field.max_length == 27
def test_deconstruct_args(self): field = SetCharField(models.CharField(max_length=5), max_length=32) name, path, args, kwargs = field.deconstruct() new = SetCharField(*args, **kwargs) assert new.base_field.max_length == field.base_field.max_length
class Team(MySqlModel): """ Attributes: :attr id: Unique team id, always equal to the team_number. :attr active_years {@set[int]}: A set of years that the team has been active (in commission, not participated) :attr website {@string}: URL for the team website :attr team_number: FRC assigned team number :attr name: Long FRC Team Name (note, this can be VERY long, short_name or nickname is recommended) :attr short_name: Short FRC Team Name (often the more wel :attr nickname: Team Nickname, e.g. Shaker Robotics or Cheesy Poofs :attr key: Most specific team name, simply frc#### where #### is the team number """ active_years = SetCharField(base_field=models.IntegerField(), max_length=255, default=set()) website = models.URLField(null=True) name = models.TextField(null=True) # longer name locality = models.CharField(max_length=50, null=True) # e.g. city region = models.CharField(max_length=50, null=True) # e.g. state, province country_name = models.CharField(max_length=50, null=True) location = models.CharField(max_length=150, null=True) # full city + state + country team_number = models.PositiveSmallIntegerField(db_index=True) key = models.CharField(max_length=8) # e.g. frc2791 nickname = models.CharField(max_length=100, null=True) # shorter name rookie_year = models.PositiveSmallIntegerField(null=True) motto = models.TextField(null=True) # TrueSkill distribution, which is a model of player skill which assumes a Gaussian distribution with # an initial estimate (mean mu, player is assumed to be of average skill) and confidence (standard deviation sigma). elo_mu = models.FloatField(default=settings.DEFAULT_MU) elo_sigma = models.FloatField(default=settings.DEFAULT_SIGMA) objects = BulkUpdateManager() # Stats that are too expensive to compute on the fly # see util.management.commands.update # Yes, it is trivially easy to get some of these through basic querysets. However, this allows us for a much # faster lookup at the expense of a very small amount of database space. active_event_winstreak = models.PositiveSmallIntegerField(default=0) longest_event_winstreak = models.PositiveSmallIntegerField(default=0) event_wins_count = models.PositiveSmallIntegerField(default=0) event_attended_count = models.PositiveSmallIntegerField(default=0) event_winrate = models.FloatField(default=0.0) match_wins_count = models.PositiveSmallIntegerField(default=0) match_losses_count = models.PositiveSmallIntegerField(default=0) match_ties_count = models.PositiveSmallIntegerField(default=0) match_winrate = models.FloatField(default=0.0) awards_count = models.PositiveSmallIntegerField(default=0) blue_banners_count = models.PositiveSmallIntegerField(default=0) def __str__(self): return "{0} ({1})".format(self.nickname, self.team_number) def get_matches(self, year=None) -> QuerySet: if year is None: return Match.objects.filter( alliances__teams__team_number=self.team_number) else: return Match.objects.filter(event__year=year).filter( alliances__teams__team_number=self.team_number) def get_wins(self, year=None) -> QuerySet: matches = Match.objects.prefetch_related('winner__teams') if year is None: return matches.filter(winner__teams__team_number=self.team_number) else: matches.select_related('event') return matches.filter(event__year=year).filter( winner__teams__team_number=self.team_number) def get_losses(self, year=None) -> QuerySet: matches = Match.objects.prefetch_related( 'alliances__teams').select_related('winner') if year is None: return matches.filter( alliances__teams__team_number=self.team_number).exclude( winner__teams__team_number=self.team_number).exclude( winner__isnull=True) else: matches.select_related('event') return matches.filter(event__year=year).filter( alliances__teams__team_number=self.team_number).exclude( winner__teams__team_number=self.team_number).exclude( winner__isnull=True) def get_ties(self, year=None) -> QuerySet: matches = Match.objects.prefetch_related( 'alliances__teams').select_related('winner') if year is None: return matches.filter( alliances__teams__team_number=self.team_number, winner__isnull=True) else: matches.select_related('event') return matches.filter(event__year=year) \ .filter(alliances__teams__team_number=self.team_number, winner__isnull=True) def get_record(self) -> str: return "{0}-{1}-{2}".format(self.match_wins_count, self.match_losses_count, self.match_ties_count) def get_winrate(self) -> float: return self.match_winrate * 100.0 def get_elo_standing(self) -> int: return Team.objects.filter(elo_mu__gte=self.elo_mu).count() def get_awards(self, year=None) -> QuerySet: if year is None: return Award.objects.filter(recipients=self) else: return Award.objects.filter(recipients=self, year=year) def count_awards_old(self) -> QuerySet: return Team.objects.filter(team_number=self.team_number).values_list('award__name'). \ annotate(count=models.Count('award__award_type')).order_by('-count') def count_awards(self) -> Dict[str, List['Event']]: awards = {} award_types = set( self.award_set.values_list('award_type', flat=True).all()) types_to_names = {t: Award.choice_to_display(t) for t in award_types} # get_events = lambda award_t: Event.objects.filter(award__award_type=award_t, award__recipients=self).values_list(flat=True) # This is great if the if statement in the below loop is not required... # awards = {types_to_names[t]: get_events(t) for t in award_types} for award_t in award_types: award = types_to_names[award_t] if award not in awards: awards[award] = Event.objects.filter( award__award_type=award_t, award__recipients=self).all() awards = {k: v for k, v in awards.items() if len(v) > 0} return OrderedDict( sorted(awards.items(), key=lambda entry: -len(entry[1]))) def get_max_opr(self) -> float: return self.rankingmodel_set.order_by('-tba_opr').first().tba_opr def to_dict(self) -> dict: return dict((name, getattr(self, name)) for name in self.__dict__ if not name.startswith('_'))
def test_char(self): field = SetCharField(models.CharField(max_length=5), max_length=32) assert field.description == "Set of String (up to %(max_length)s)"
class Film(models.Model, ModelSerializationMixin): """ CREATE TABLE film ( film_id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, title VARCHAR(128) NOT NULL, description TEXT DEFAULT NULL, release_year YEAR DEFAULT NULL, language_id TINYINT UNSIGNED NOT NULL, original_language_id TINYINT UNSIGNED DEFAULT NULL, rental_duration TINYINT UNSIGNED NOT NULL DEFAULT 3, rental_rate DECIMAL(4,2) NOT NULL DEFAULT 4.99, length SMALLINT UNSIGNED DEFAULT NULL, replacement_cost DECIMAL(5,2) NOT NULL DEFAULT 19.99, rating ENUM('G','PG','PG-13','R','NC-17') DEFAULT 'G', special_features SET('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') DEFAULT NULL, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (film_id), KEY idx_title (title), KEY idx_fk_language_id (language_id), KEY idx_fk_original_language_id (original_language_id), CONSTRAINT fk_film_language FOREIGN KEY (language_id) REFERENCES language (language_id) ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT fk_film_language_original FOREIGN KEY (original_language_id) REFERENCES language (language_id) ON DELETE RESTRICT ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; # Django会自动为所有 models.ForeignKey 列创建索引 """ film_id = models.SmallAutoField(primary_key=True) title = models.CharField(max_length=128) description = models.TextField(null=True, default=None) YEAR_CHOICES = lambda : [(r, r) for r in range(1901, 2156)] release_year = models.PositiveSmallIntegerField(choices=YEAR_CHOICES() , null=True, default=None) language = models.ForeignKey(Language, on_delete=models.RESTRICT) original_language = models.ForeignKey( Language, on_delete=models.RESTRICT, related_name="original_film", null=True, default=None, ) rental_duration = models.PositiveSmallIntegerField(default=3) rental_rate = models.DecimalField(max_digits=4, decimal_places=2, default='4.99') length = models.PositiveSmallIntegerField(null=True, default=None) replacement_cost = models.DecimalField(max_digits=5, decimal_places=2, default='19.99') class Rating(ChoiceEnum): G = 'G' R = 'R' PG = 'PG' PG_13 = 'PG-13' NC_17 = 'NC-17' rating = models.CharField(max_length=5, choices=Rating, default=Rating.G) class Special(ChoiceEnum): Trailers = 'Trailers' Commentaries = 'Commentaries' Deleted_Scenes = 'Deleted Scenes' Behind_the_Scenes = 'Behind the Scenes' """ 生成数据表后, 使用该语句修改字段类型即可符合表原型设计, 并且兼容SetCharField ALTER TABLE film MODIFY special_features SET('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') DEFAULT NULL COLLATE utf8mb4_general_ci; 设置字段的字符集排序的原因是 Film.objects.filter(special_features__contains="trailers").update( special_features=SetF('special_features').add('Commentaries') ) 会触发以下报错: MySQLdb._exceptions.OperationalError: (1267, "Illegal mix of collations (utf8mb4_general_ci,IMPLICIT) and (utf8mb4_unicode_ci,IMPLICIT) for operation 'find_in_set'") 因为字符集排序规则不一致导致. """ special_features = SetCharField( base_field=models.CharField(max_length=20), size=4, max_length=83, # 20*4+3个逗号, 这里是设置数据库该字段的最终长度 choices=Special, # 该设置在model层无效 null=True, default=None, verbose_name="特殊功能" ) actors = models.ManyToManyField(Actor, through='FilmActor', verbose_name='电影演员') last_update = models.DateTimeField(auto_now=True) class Meta: verbose_name = "电影表" verbose_name_plural = verbose_name db_table = "film" indexes = [models.Index(fields=['title'], name='idx_title')]
class InvalidSetCharFieldModel2(TemporaryModel): field = SetCharField(models.ForeignKey('testapp.Author'), max_length=32)
def test_model_field_formfield_size(self): model_field = SetCharField(models.IntegerField(), size=4) form_field = model_field.formfield() assert isinstance(form_field, SimpleSetField) assert form_field.max_length == 4
def test_deconstruct_with_size(self): field = SetCharField(models.IntegerField(), size=3, max_length=32) name, path, args, kwargs = field.deconstruct() new = SetCharField(*args, **kwargs) assert new.size == field.size
class Invalid(models.Model): field = SetCharField(models.CharField(max_length=2), size=2)
class IntSetModel(Model): field = SetCharField(base_field=IntegerField(), size=5, max_length=32)
def test_int(self): field = SetCharField(models.IntegerField(), max_length=32) assert field.description == "Set of Integer"
class Invalid(models.Model): field = SetCharField(models.CharField(), max_length=32)