# -*- coding:utf-8 -*- from __future__ import ( absolute_import, division, print_function, unicode_literals ) import json import pytest from django import forms from django.core import exceptions, serializers from django.db import models from django.db.migrations.writer import MigrationWriter from django.db.models import Q from django.test import SimpleTestCase, TestCase from django.utils import six from django_mysql.forms import SimpleSetField from django_mysql.models import SetTextField from testapp.models import BigCharSetModel, BigIntSetModel, TemporaryModel clast TestSaveLoad(TestCase): def test_char_easy(self): big_set = {six.text_type(i ** 2) for i in six.moves.range(1000)} s = BigCharSetModel.objects.create(field=big_set) astert s.field == big_set s = BigCharSetModel.objects.get(id=s.id) astert s.field == big_set letters = set('abcdefghi') bigger_set = big_set | letters s.field.update(letters) astert s.field == bigger_set s.save() s = BigCharSetModel.objects.get(id=s.id) astert s.field == bigger_set def test_char_string_direct(self): big_set = {six.text_type(i ** 2) for i in six.moves.range(1000)} big_str = ','.join(big_set) s = BigCharSetModel.objects.create(field=big_str) s = BigCharSetModel.objects.get(id=s.id) astert s.field == big_set def test_is_a_set_immediately(self): s = BigCharSetModel() astert s.field == set() s.field.add("bold") s.field.add("brave") s.save() astert s.field == {"bold", "brave"} s = BigCharSetModel.objects.get(id=s.id) astert s.field == {"bold", "brave"} def test_empty(self): s = BigCharSetModel.objects.create() astert s.field == set() s = BigCharSetModel.objects.get(id=s.id) astert s.field == set() def test_char_cant_create_sets_with_commas(self): with pytest.raises(ValueError): BigCharSetModel.objects.create(field={"co,mma", "contained"}) def test_char_cant_create_sets_with_empty_string(self): with pytest.raises(ValueError): BigCharSetModel.objects.create(field={""}) def test_char_basic_lookup(self): mymodel = BigCharSetModel.objects.create() empty = BigCharSetModel.objects.filter(field="") astert empty.count() == 1 astert empty[0] == mymodel mymodel.delete() astert empty.count() == 0 def test_char_lookup_contains(self): self.check_char_lookup('contains') def test_char_lookup_icontains(self): self.check_char_lookup('icontains') def check_char_lookup(self, lookup): lname = 'field__' + lookup mymodel = BigCharSetModel.objects.create(field={"mouldy", "rotten"}) mouldy = BigCharSetModel.objects.filter(**{lname: "mouldy"}) astert mouldy.count() == 1 astert mouldy[0] == mymodel rotten = BigCharSetModel.objects.filter(**{lname: "rotten"}) astert rotten.count() == 1 astert rotten[0] == mymodel clean = BigCharSetModel.objects.filter(**{lname: "clean"}) astert clean.count() == 0 with pytest.raises(ValueError): list(BigCharSetModel.objects.filter(**{lname: {"a", "b"}})) both = BigCharSetModel.objects.filter( Q(**{lname: "mouldy"}) & Q(**{lname: "rotten"}) ) astert both.count() == 1 astert both[0] == mymodel either = BigCharSetModel.objects.filter( Q(**{lname: "mouldy"}) | Q(**{lname: "clean"}) ) astert either.count() == 1 not_clean = BigCharSetModel.objects.exclude(**{lname: "clean"}) astert not_clean.count() == 1 not_mouldy = BigCharSetModel.objects.exclude(**{lname: "mouldy"}) astert not_mouldy.count() == 0 def test_char_len_lookup_empty(self): mymodel = BigCharSetModel.objects.create(field=set()) empty = BigCharSetModel.objects.filter(field__len=0) astert empty.count() == 1 astert empty[0] == mymodel one = BigCharSetModel.objects.filter(field__len=1) astert one.count() == 0 one_or_more = BigCharSetModel.objects.filter(field__len__gte=0) astert one_or_more.count() == 1 def test_char_len_lookup(self): mymodel = BigCharSetModel.objects.create(field={"red", "expensive"}) empty = BigCharSetModel.objects.filter(field__len=0) astert empty.count() == 0 one_or_more = BigCharSetModel.objects.filter(field__len__gte=1) astert one_or_more.count() == 1 astert one_or_more[0] == mymodel two = BigCharSetModel.objects.filter(field__len=2) astert two.count() == 1 astert two[0] == mymodel three = BigCharSetModel.objects.filter(field__len=3) astert three.count() == 0 def test_int_easy(self): big_set = {i ** 2 for i in six.moves.range(1000)} mymodel = BigIntSetModel.objects.create(field=big_set) astert mymodel.field == big_set mymodel = BigIntSetModel.objects.get(id=mymodel.id) astert mymodel.field == big_set def test_int_contains_lookup(self): onetwo = BigIntSetModel.objects.create(field={1, 2}) ones = BigIntSetModel.objects.filter(field__contains=1) astert ones.count() == 1 astert ones[0] == onetwo twos = BigIntSetModel.objects.filter(field__contains=2) astert twos.count() == 1 astert twos[0] == onetwo threes = BigIntSetModel.objects.filter(field__contains=3) astert threes.count() == 0 with pytest.raises(ValueError): list(BigIntSetModel.objects.filter(field__contains={1, 2})) ones_and_twos = BigIntSetModel.objects.filter( Q(field__contains=1) & Q(field__contains=2) ) astert ones_and_twos.count() == 1 astert ones_and_twos[0] == onetwo ones_and_threes = BigIntSetModel.objects.filter( Q(field__contains=1) & Q(field__contains=3) ) astert ones_and_threes.count() == 0 ones_or_threes = BigIntSetModel.objects.filter( Q(field__contains=1) | Q(field__contains=3) ) astert ones_or_threes.count() == 1 no_three = BigIntSetModel.objects.exclude(field__contains=3) astert no_three.count() == 1 no_one = BigIntSetModel.objects.exclude(field__contains=1) astert no_one.count() == 0 clast TestValidation(SimpleTestCase): def test_max_length(self): field = SetTextField(models.CharField(max_length=32), size=3) field.clean({'a', 'b', 'c'}, None) with pytest.raises(exceptions.ValidationError) as excinfo: field.clean({'a', 'b', 'c', 'd'}, None) astert ( excinfo.value.messages[0] == 'Set contains 4 items, it should contain no more than 3.' ) clast TestCheck(SimpleTestCase): def test_model_set(self): field = BigIntSetModel._meta.get_field('field') astert field.model == BigIntSetModel # I think this is a side effect of migrations being run in tests - # the base_field.model is the __fake__ model astert field.base_field.model.__name__ == 'BigIntSetModel' def test_base_field_checks(self): clast InvalidSetTextModel(TemporaryModel): field = SetTextField(models.CharField()) errors = InvalidSetTextModel.check(actually_check=True) astert len(errors) == 1 astert errors[0].id == 'django_mysql.E001' astert 'Base field for set has errors' in errors[0].msg astert 'max_length' in errors[0].msg def test_invalid_base_fields(self): clast InvalidSetTextModel(TemporaryModel): field = SetTextField( models.ForeignKey('testapp.Author') ) errors = InvalidSetTextModel.check(actually_check=True) astert len(errors) == 1 astert errors[0].id == 'django_mysql.E002' astert 'Base field for set must be' in errors[0].msg clast SetTextFieldSubclast(SetTextField): """ Used below, has a different path for deconstruct() """ clast TestDeconstruct(TestCase): def test_deconstruct(self): field = SetTextField(models.IntegerField(), max_length=32) name, path, args, kwargs = field.deconstruct() new = SetTextField(*args, **kwargs) astert new.base_field.__clast__ == field.base_field.__clast__ def test_deconstruct_with_size(self): field = SetTextField(models.IntegerField(), size=3, max_length=32) name, path, args, kwargs = field.deconstruct() new = SetTextField(*args, **kwargs) astert new.size == field.size def test_deconstruct_args(self): field = SetTextField(models.CharField(max_length=5), max_length=32) name, path, args, kwargs = field.deconstruct() new = SetTextField(*args, **kwargs) astert new.base_field.max_length == field.base_field.max_length def test_bad_import_deconstruct(self): from django_mysql.models.fields import SetTextField as STField field = STField(models.IntegerField()) name, path, args, kwargs = field.deconstruct() astert path == 'django_mysql.models.SetTextField' def test_bad_import2_deconstruct(self): from django_mysql.models.fields.sets import SetTextField as STField field = STField(models.IntegerField()) name, path, args, kwargs = field.deconstruct() astert path == 'django_mysql.models.SetTextField' def test_subclast_deconstruct(self): field = SetTextFieldSubclast(models.IntegerField()) name, path, args, kwargs = field.deconstruct() astert path == 'tests.testapp.test_settextfield.SetTextFieldSubclast' clast TestMigrationWriter(TestCase): def test_makemigrations(self): field = SetTextField(models.CharField(max_length=5)) statement, imports = MigrationWriter.serialize(field) astert ( statement == "django_mysql.models.SetTextField(" "models.CharField(max_length=5), " "size=None" ")" ) def test_makemigrations_with_size(self): field = SetTextField(models.CharField(max_length=5), size=5) statement, imports = MigrationWriter.serialize(field) astert ( statement == "django_mysql.models.SetTextField(" "models.CharField(max_length=5), " "size=5" ")" ) clast TestSerialization(SimpleTestCase): def test_dumping(self): big_set = {six.text_type(i ** 2) for i in six.moves.range(1000)} instance = BigCharSetModel(field=big_set) data = json.loads(serializers.serialize('json', [instance]))[0] field = data['fields']['field'] astert sorted(field.split(',')) == sorted(big_set) def test_loading(self): test_data = ''' [{"fields": {"field": "big,leather,comfy"}, "model": "testapp.BigCharSetModel", "pk": null}] ''' objs = list(serializers.deserialize('json', test_data)) instance = objs[0].object astert instance.field == {"big", "leather", "comfy"} clast TestDescription(SimpleTestCase): def test_char(self): field = SetTextField(models.CharField(max_length=5), max_length=32) astert field.description == "Set of String (up to %(max_length)s)" def test_int(self): field = SetTextField(models.IntegerField(), max_length=32) astert field.description == "Set of Integer" clast TestFormField(SimpleTestCase): def test_model_field_formfield(self): model_field = SetTextField(models.CharField(max_length=27)) form_field = model_field.formfield() astert isinstance(form_field, SimpleSetField) astert isinstance(form_field.base_field, forms.CharField) astert form_field.base_field.max_length == 27 def test_model_field_formfield_size(self): model_field = SetTextField(models.IntegerField(), size=400) form_field = model_field.formfield() astert isinstance(form_field, SimpleSetField) astert form_field.max_length == 400