diff --git a/canopeum_backend/canopeum_backend/management/commands/initialize_database.py b/canopeum_backend/canopeum_backend/management/commands/initialize_database.py index 93105ede8..648120c19 100644 --- a/canopeum_backend/canopeum_backend/management/commands/initialize_database.py +++ b/canopeum_backend/canopeum_backend/management/commands/initialize_database.py @@ -1,6 +1,7 @@ # flake8: noqa: S311 -- Accept random int generation for database seeding import random +from collections.abc import Iterable from datetime import timedelta from pathlib import Path @@ -29,6 +30,7 @@ Role, Site, Siteadmin, + Sitetreespecies, Sitetype, SitetypeInternationalization, TreespeciestypeInternationalization, @@ -269,12 +271,32 @@ def create_sponsor_for_batch(): ) +def create_species_for_site(site: Site, batches: Iterable[Batch]): + already_added_tree_type: dict[int, Sitetreespecies] = {} + for batch in batches: + for batch_specie in BatchSpecies.objects.filter(batch=batch): + quantity = batch_specie.quantity + # Add more to the site's quantity than the batches' quantity + # so they don't appear at 100%. Except Canopeum, let's use it as a 100% example + if site.name != "Canopeum": + quantity += random.randint(0, 50) + if batch_specie.tree_type.pk in already_added_tree_type: + site_tree_specie = already_added_tree_type[batch_specie.tree_type.pk] + site_tree_specie.quantity += quantity + site_tree_specie.save() + else: + site_tree_specie = Sitetreespecies.objects.create( + site=site, tree_type=batch_specie.tree_type, quantity=quantity + ) + already_added_tree_type[batch_specie.tree_type.pk] = site_tree_specie + + def create_batches_for_site(site): num_batches = random.randint(3, 8) for i in range(num_batches): number_of_seed = random.randint(50, 200) - plant_count = random.randint(0, number_of_seed) - survived_count = random.randint(0, plant_count) + survived_count = random.randint(100, 200) + replace_count = random.randint(0, 50) sponsor = create_sponsor_for_batch() @@ -284,9 +306,8 @@ def create_batches_for_site(site): size=random.randint(20, 150), sponsor=sponsor, soil_condition="Good", - plant_count=plant_count, survived_count=survived_count, - replace_count=plant_count - survived_count, + replace_count=replace_count, total_number_seed=number_of_seed, total_propagation=random.randint(0, number_of_seed), ) @@ -297,6 +318,7 @@ def create_batches_for_site(site): batch=batch, fertilizer_type=fertilizer_type, ) + yield batch class Command(BaseCommand): @@ -352,8 +374,7 @@ def handle(self, *args, **kwargs): self.create_roles() self.create_users() - self.create_canopeum_site() - self.create_other_sites() + self.create_sites() self.create_siteadmins() self.stdout.write(self.style.SUCCESS("Data Generated")) @@ -479,8 +500,9 @@ def create_users(self): role=Role.objects.get(name="User"), ) - def create_canopeum_site(self): - site = Site.objects.create( + def create_sites(self): + # Canopeum's site + site1 = Site.objects.create( name="Canopeum", is_public=True, site_type=Sitetype.objects.get( @@ -511,15 +533,16 @@ def create_canopeum_site(self): link="https://www.canopeum-pos.com", ), ) - create_batches_for_site(site) + batches = create_batches_for_site(site1) + create_species_for_site(site1, batches) post = Post.objects.create( - site=site, + site=site1, body="The season is officially started; " + "new plants are starting to grow and our volunteers are very dedicated!", share_count=5, ) post.media.add(*Asset.objects.filter(asset__contains="canopeum_post_img")) - create_posts_for_site(site) + create_posts_for_site(site1) Comment.objects.create( body="Wow, I'm very excited to join the team!", user=User.objects.get(email="tyrion@lannister.com"), @@ -530,8 +553,8 @@ def create_canopeum_site(self): user=User.objects.get(email="normal@user.com"), post=post, ) + # end of Canopeum's site - def create_other_sites(self): site_2 = Site.objects.create( name="Maple Grove Retreat", is_public=True, @@ -564,7 +587,8 @@ def create_other_sites(self): link="https://www.maplegroveretreat.com/events/maple-syrup-festival", ), ) - create_batches_for_site(site_2) + batches = create_batches_for_site(site_2) + create_species_for_site(site_2, batches) create_posts_for_site(site_2) site_3 = Site.objects.create( @@ -600,7 +624,8 @@ def create_other_sites(self): link="https://www.lakesideoasis.com/winter-getaway", ), ) - create_batches_for_site(site_3) + batches = create_batches_for_site(site_3) + create_species_for_site(site_3, batches) create_posts_for_site(site_3) site_4 = Site.objects.create( @@ -636,7 +661,8 @@ def create_other_sites(self): link="https://www.evergreentrail.com/guided-walks", ), ) - create_batches_for_site(site_4) + batches = create_batches_for_site(site_4) + create_species_for_site(site_4, batches) create_posts_for_site(site_4) def create_siteadmins(self): diff --git a/canopeum_backend/canopeum_backend/migrations/0001_initial.py b/canopeum_backend/canopeum_backend/migrations/0001_initial.py index af3c926aa..365ee675b 100644 --- a/canopeum_backend/canopeum_backend/migrations/0001_initial.py +++ b/canopeum_backend/canopeum_backend/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1 on 2024-09-26 20:15 +# Generated by Django 5.1 on 2024-09-30 16:29 import canopeum_backend.models import django.contrib.auth.models @@ -232,7 +232,6 @@ class Migration(migrations.Migration): ('name', models.TextField(blank=True, null=True)), ('size', models.IntegerField(blank=True, null=True)), ('soil_condition', models.TextField(blank=True, null=True)), - ('plant_count', models.IntegerField(blank=True, null=True)), ('survived_count', models.IntegerField(blank=True, null=True)), ('replace_count', models.IntegerField(blank=True, null=True)), ('total_number_seed', models.IntegerField(blank=True, null=True)), @@ -316,9 +315,9 @@ class Migration(migrations.Migration): name='Sitetreespecies', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('quantity', models.IntegerField(blank=True, null=True)), - ('site', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='canopeum_backend.site')), - ('tree_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='canopeum_backend.treetype')), + ('quantity', models.IntegerField()), + ('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='canopeum_backend.site')), + ('tree_type', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='canopeum_backend.treetype')), ], options={ 'constraints': [models.UniqueConstraint(fields=('site', 'tree_type'), name='unique_tree_species_per_site')], diff --git a/canopeum_backend/canopeum_backend/models.py b/canopeum_backend/canopeum_backend/models.py index 56f667d28..7dc62d509 100644 --- a/canopeum_backend/canopeum_backend/models.py +++ b/canopeum_backend/canopeum_backend/models.py @@ -156,6 +156,21 @@ class Site(models.Model): announcement = models.ForeignKey(Announcement, models.SET_NULL, blank=True, null=True) image = models.ForeignKey(Asset, models.SET_NULL, blank=True, null=True) + def get_plant_count(self) -> int: + site_species = Sitetreespecies.objects.filter(site=self) + return sum(specie.quantity for specie in site_species) + + def get_sponsor_progress(self) -> float: + total_plant_count = self.get_plant_count() + if total_plant_count == 0: + return 0 + + batches = Batch.objects.filter(site=self) + sponsored_plant_count = sum(batch.plant_count() for batch in batches) + + # Note: We don't cap the progress at 100% so it's obvious if there's a data issue + return sponsored_plant_count / total_plant_count * 100 + @override def delete(self, using=None, keep_parents=False): # Coordinate @@ -191,7 +206,6 @@ class Batch(models.Model): sponsor = models.ForeignKey(BatchSponsor, models.CASCADE) size = models.IntegerField(blank=True, null=True) soil_condition = models.TextField(blank=True, null=True) - plant_count = models.IntegerField(blank=True, null=True) survived_count = models.IntegerField(blank=True, null=True) replace_count = models.IntegerField(blank=True, null=True) total_number_seed = models.IntegerField(blank=True, null=True) @@ -218,6 +232,10 @@ def add_supported_specie_by_id(self, pk: int): tree_type = Treetype.objects.get(pk=pk) return BatchSupportedSpecies.objects.create(tree_type=tree_type, batch=self) + def plant_count(self) -> int: + batch_species = BatchSpecies.objects.filter(batch=self) + return sum(specie.quantity for specie in batch_species) + class FertilizertypeInternationalization(models.Model): en = models.TextField(db_column="EN", blank=True, null=True) @@ -362,9 +380,9 @@ class SiteFollower(models.Model): class Sitetreespecies(models.Model): - site = models.ForeignKey(Site, models.CASCADE, blank=True, null=True) - tree_type = models.ForeignKey(Treetype, models.DO_NOTHING, blank=True, null=True) - quantity = models.IntegerField(blank=True, null=True) + site = models.ForeignKey(Site, models.CASCADE) + tree_type = models.ForeignKey(Treetype, models.DO_NOTHING) + quantity = models.IntegerField() class Meta: constraints = ( diff --git a/canopeum_backend/canopeum_backend/serializers.py b/canopeum_backend/canopeum_backend/serializers.py index a1f96cbcc..47572696e 100644 --- a/canopeum_backend/canopeum_backend/serializers.py +++ b/canopeum_backend/canopeum_backend/serializers.py @@ -543,9 +543,9 @@ class SiteSummarySerializer(serializers.ModelSerializer[Site]): site_type = SiteTypeSerializer() coordinate = CoordinatesSerializer() plant_count = serializers.SerializerMethodField() + sponsor_progress = serializers.SerializerMethodField() survived_count = serializers.SerializerMethodField() propagation_count = serializers.SerializerMethodField() - progress = serializers.SerializerMethodField() admins = SiteAdminSerializer(source="siteadmin_set", many=True) batches = serializers.SerializerMethodField() @@ -557,16 +557,19 @@ class Meta: "coordinate", "site_type", "plant_count", + "sponsor_progress", "survived_count", "propagation_count", "visitor_count", - "progress", "admins", "batches", ) - def get_plant_count(self, obj) -> int: - return random.randint(100, 200) # noqa: S311 + def get_plant_count(self, obj: Site) -> int: + return obj.get_plant_count() + + def get_sponsor_progress(self, obj: Site) -> float: + return obj.get_sponsor_progress() def get_survived_count(self, obj) -> int: return random.randint(50, 100) # noqa: S311 @@ -574,9 +577,6 @@ def get_survived_count(self, obj) -> int: def get_propagation_count(self, obj) -> int: return random.randint(5, 50) # noqa: S311 - def get_progress(self, obj) -> float: - return random.randint(0, 10000) / 100 # noqa: S311 - @extend_schema_field(BatchDetailSerializer(many=True)) def get_batches(self, obj): batches = obj.batch_set.all().order_by("-updated_at") @@ -587,9 +587,9 @@ class SiteSummaryDetailSerializer(serializers.ModelSerializer[Site]): site_type = SiteTypeSerializer() coordinate = CoordinatesSerializer() plant_count = serializers.SerializerMethodField() + sponsor_progress = serializers.SerializerMethodField() survived_count = serializers.SerializerMethodField() propagation_count = serializers.SerializerMethodField() - progress = serializers.SerializerMethodField() sponsors = serializers.SerializerMethodField() admins = SiteAdminSerializer(source="siteadmin_set", many=True) batches = serializers.SerializerMethodField() @@ -603,18 +603,21 @@ class Meta: "coordinate", "site_type", "plant_count", + "sponsor_progress", "survived_count", "propagation_count", "visitor_count", "sponsors", - "progress", "admins", "batches", "weather", ) - def get_plant_count(self, obj) -> int: - return random.randint(100, 200) # noqa: S311 + def get_plant_count(self, obj: Site) -> int: + return obj.get_plant_count() + + def get_sponsor_progress(self, obj: Site) -> float: + return obj.get_sponsor_progress() def get_survived_count(self, obj) -> int: return random.randint(50, 100) # noqa: S311 @@ -622,9 +625,6 @@ def get_survived_count(self, obj) -> int: def get_propagation_count(self, obj) -> int: return random.randint(5, 50) # noqa: S311 - def get_progress(self, obj) -> float: - return random.randint(0, 10000) / 100 # noqa: S311 - @extend_schema_field(BatchSponsorSerializer(many=True)) def get_sponsors(self, obj): batches = Batch.objects.filter(site=obj) diff --git a/canopeum_frontend/src/components/analytics/AnalyticsSiteHeader.tsx b/canopeum_frontend/src/components/analytics/AnalyticsSiteHeader.tsx index 7d01c6937..75f41e49c 100644 --- a/canopeum_frontend/src/components/analytics/AnalyticsSiteHeader.tsx +++ b/canopeum_frontend/src/components/analytics/AnalyticsSiteHeader.tsx @@ -99,7 +99,7 @@ const AnalyticsSiteHeader = ({ siteSummary }: Props) => {