diff --git a/vbos/datasets/migrations/0013_alter_rasterdataset_name_alter_tabulardataset_name_and_more.py b/vbos/datasets/migrations/0013_alter_rasterdataset_name_alter_tabulardataset_name_and_more.py new file mode 100644 index 0000000..88ba9b0 --- /dev/null +++ b/vbos/datasets/migrations/0013_alter_rasterdataset_name_alter_tabulardataset_name_and_more.py @@ -0,0 +1,40 @@ +# Generated by Django 5.2.5 on 2025-10-09 12:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("datasets", "0012_tabulardataset_unit"), + ] + + operations = [ + migrations.AlterField( + model_name="rasterdataset", + name="name", + field=models.CharField(max_length=155), + ), + migrations.AlterField( + model_name="tabulardataset", + name="name", + field=models.CharField(max_length=155), + ), + migrations.AlterField( + model_name="vectordataset", + name="name", + field=models.CharField(max_length=155), + ), + migrations.AlterUniqueTogether( + name="rasterdataset", + unique_together={("name", "type")}, + ), + migrations.AlterUniqueTogether( + name="tabulardataset", + unique_together={("name", "type")}, + ), + migrations.AlterUniqueTogether( + name="vectordataset", + unique_together={("name", "type")}, + ), + ] diff --git a/vbos/datasets/models.py b/vbos/datasets/models.py index e07d1fc..a58ec0f 100644 --- a/vbos/datasets/models.py +++ b/vbos/datasets/models.py @@ -81,7 +81,7 @@ def delete_raster_file(sender, instance, **kwargs): class RasterDataset(models.Model): - name = models.CharField(max_length=155, unique=True) + name = models.CharField(max_length=155) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) cluster = models.ForeignKey( @@ -97,10 +97,11 @@ def __str__(self): class Meta: ordering = ["id"] + unique_together = ["name", "type"] class VectorDataset(models.Model): - name = models.CharField(max_length=155, unique=True) + name = models.CharField(max_length=155, unique=False) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) cluster = models.ForeignKey( @@ -115,6 +116,7 @@ def __str__(self): class Meta: ordering = ["id"] + unique_together = ["name", "type"] class VectorItem(models.Model): @@ -138,7 +140,7 @@ class Meta: class TabularDataset(models.Model): - name = models.CharField(max_length=155, unique=True) + name = models.CharField(max_length=155, unique=False) created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) cluster = models.ForeignKey( @@ -154,6 +156,7 @@ def __str__(self): class Meta: ordering = ["id"] + unique_together = ["name", "type"] class TabularItem(models.Model): diff --git a/vbos/datasets/test/test_models.py b/vbos/datasets/test/test_models.py new file mode 100644 index 0000000..da32896 --- /dev/null +++ b/vbos/datasets/test/test_models.py @@ -0,0 +1,137 @@ +from django.db.models.deletion import ProtectedError +from django.db.utils import IntegrityError +from django.test import TestCase +from django.core.files.uploadedfile import SimpleUploadedFile +from django.core.exceptions import ValidationError + +from vbos.datasets.models import ( + Cluster, + RasterDataset, + RasterFile, + TabularDataset, + VectorDataset, +) + + +class TestRasterModels(TestCase): + def setUp(self): + self.valid_file = SimpleUploadedFile( + "rainfall.tiff", b"file_content", content_type="image/tiff" + ) + self.r_1 = RasterFile.objects.create(name="Rainfall COG", file=self.valid_file) + self.r_2 = RasterFile.objects.create( + name="Coastline COG", file="raster/coastline.tiff" + ) + self.dataset = RasterDataset.objects.create( + name="Rainfall", + cluster=Cluster.objects.create(name="Environment"), + file=self.r_1, + ) + + def test_deletion(self): + # RasterFile can't be deleted if it's associates with a dataset + with self.assertRaises(ProtectedError): + self.r_1.delete() + + # name should be unique + raster = RasterFile(name="Rainfall COG 2", file="raster/coastline.tiff") + with self.assertRaises(ValidationError): + raster.full_clean() + + # file path should be unique + raster = RasterFile(name="Rainfall COG", file="newfile.tif") + with self.assertRaises(ValidationError): + raster.full_clean() + + # modify dataset + self.dataset.file = self.r_2 + self.dataset.save() + # delete file + self.r_1.delete() + self.assertEqual(RasterFile.objects.count(), 1) + # delete dataset + self.dataset.delete() + self.assertEqual(RasterDataset.objects.count(), 0) + # delete remaining file + self.r_2.delete() + self.assertEqual(RasterFile.objects.count(), 0) + + # test file extension validation + invalid_file = SimpleUploadedFile( + "test.jpg", b"file_content", content_type="image/jpeg" + ) + raster = RasterFile(name="Test", file=invalid_file) + with self.assertRaises(ValidationError): + raster.full_clean() + + RasterFile.objects.all().delete() + + def test_unique_name_type(self): + self.cluster = Cluster.objects.create(name="Administrative") + r_2 = RasterFile.objects.create( + name="Population Density COG", file="raster/pop.tiff" + ) + RasterDataset.objects.create( + name="Population", + cluster=self.cluster, + source="Government", + file=r_2, + ) + RasterDataset.objects.create( + name="Population", + cluster=self.cluster, + source="Government", + file=r_2, + type="estimated_damage", + ) + with self.assertRaises(IntegrityError): + RasterDataset.objects.create( + name="Population", + cluster=self.cluster, + source="Government", + file=r_2, + ) + + +class TestTabularDatasetModel(TestCase): + def test_unique_name_type(self): + self.cluster = Cluster.objects.create(name="Administrative") + TabularDataset.objects.create( + name="Population", + cluster=self.cluster, + source="Government", + ) + TabularDataset.objects.create( + name="Population", + cluster=self.cluster, + source="Government", + type="estimated_damage", + ) + with self.assertRaises(IntegrityError): + TabularDataset.objects.create( + name="Population", + cluster=self.cluster, + source="Government", + ) + + +class TestVectorDatasetModel(TestCase): + def test_unique_name_type(self): + self.cluster = Cluster.objects.create(name="Administrative") + VectorDataset.objects.create( + name="Population", + cluster=self.cluster, + source="Government", + ) + VectorDataset.objects.create( + name="Population", + cluster=self.cluster, + source="Government", + type="estimated_damage", + ) + with self.assertRaises(IntegrityError): + VectorDataset.objects.create( + name="Population", + cluster=self.cluster, + source="Government", + ) diff --git a/vbos/datasets/test/test_raster_models.py b/vbos/datasets/test/test_raster_models.py deleted file mode 100644 index ef89dd8..0000000 --- a/vbos/datasets/test/test_raster_models.py +++ /dev/null @@ -1,61 +0,0 @@ -from django.db.models.deletion import ProtectedError -from django.test import TestCase -from django.core.files.uploadedfile import SimpleUploadedFile -from django.core.exceptions import ValidationError - -from vbos.datasets.models import Cluster, RasterDataset, RasterFile - - -class TestRasterModels(TestCase): - def setUp(self): - self.valid_file = SimpleUploadedFile( - "rainfall.tiff", b"file_content", content_type="image/tiff" - ) - self.r_1 = RasterFile.objects.create(name="Rainfall COG", file=self.valid_file) - self.r_2 = RasterFile.objects.create( - name="Coastline COG", file="raster/coastline.tiff" - ) - self.dataset = RasterDataset.objects.create( - name="Rainfall", - cluster=Cluster.objects.create(name="Environment"), - file=self.r_1, - ) - - def test_deletion(self): - # RasterFile can't be deleted if it's associates with a dataset - with self.assertRaises(ProtectedError): - self.r_1.delete() - - # name should be unique - raster = RasterFile(name="Rainfall COG 2", file="raster/coastline.tiff") - with self.assertRaises(ValidationError): - raster.full_clean() - - # file path should be unique - raster = RasterFile(name="Rainfall COG", file="newfile.tif") - with self.assertRaises(ValidationError): - raster.full_clean() - - # modify dataset - self.dataset.file = self.r_2 - self.dataset.save() - # delete file - self.r_1.delete() - self.assertEqual(RasterFile.objects.count(), 1) - # delete dataset - self.dataset.delete() - self.assertEqual(RasterDataset.objects.count(), 0) - # delete remaining file - self.r_2.delete() - self.assertEqual(RasterFile.objects.count(), 0) - - # test file extension validation - invalid_file = SimpleUploadedFile( - "test.jpg", b"file_content", content_type="image/jpeg" - ) - raster = RasterFile(name="Test", file=invalid_file) - with self.assertRaises(ValidationError): - raster.full_clean() - - def tearDown(self): - RasterFile.objects.all().delete()