diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 230f107..c6e9b88 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/settings.py b/config/settings.py index 2a6d7a7..666ac00 100644 --- a/config/settings.py +++ b/config/settings.py @@ -13,7 +13,11 @@ DEBUG = os.environ.get("DEBUG", "True") == "True" ALLOWED_HOSTS = ["*"] # CSRF settings for Flatlogic Cloud -CSRF_TRUSTED_ORIGINS = ["https://*.flatlogic.app", "https://*.flatlogic.com"] +CSRF_TRUSTED_ORIGINS = [ + "https://*.flatlogic.app", + "https://*.flatlogic.com", + "https://naims-5b7f.dev.flatlogic.app" +] INSTALLED_APPS = [ "django.contrib.admin", @@ -96,3 +100,8 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" LOGIN_REDIRECT_URL = "home" LOGOUT_REDIRECT_URL = "home" + +# Security settings for proxy +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +CSRF_COOKIE_SECURE = True +SESSION_COOKIE_SECURE = True diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 2b503a8..b3d1b13 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 37ef93b..414e5dc 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 56d0307..433e61f 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 1130bf2..f182200 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,5 +1,32 @@ from django.contrib import admin -from .models import Region, Constituency, Farmer, AgriculturalHolding +from .models import ( + Region, Constituency, Farmer, AgriculturalHolding, + CropProduction, LivestockProduction, Forestry, Fishery, LandRegistry +) + +class CropProductionInline(admin.TabularInline): + model = CropProduction + extra = 1 + +class LivestockProductionInline(admin.TabularInline): + model = LivestockProduction + extra = 1 + +class ForestryInline(admin.TabularInline): + model = Forestry + extra = 1 + +class FisheryInline(admin.TabularInline): + model = Fishery + extra = 1 + +class LandRegistryInline(admin.TabularInline): + model = LandRegistry + extra = 1 + +class AgriculturalHoldingInline(admin.TabularInline): + model = AgriculturalHolding + extra = 1 @admin.register(Region) class RegionAdmin(admin.ModelAdmin): @@ -17,8 +44,40 @@ class FarmerAdmin(admin.ModelAdmin): list_display = ('name', 'id_number', 'constituency', 'created_at') list_filter = ('constituency__region', 'constituency') search_fields = ('name', 'id_number') + inlines = [AgriculturalHoldingInline, LandRegistryInline] @admin.register(AgriculturalHolding) class HoldingAdmin(admin.ModelAdmin): list_display = ('farmer', 'primary_activity', 'size_hectares') - list_filter = ('primary_activity',) \ No newline at end of file + list_filter = ('primary_activity',) + inlines = [CropProductionInline, LivestockProductionInline, ForestryInline, FisheryInline] + +@admin.register(CropProduction) +class CropProductionAdmin(admin.ModelAdmin): + list_display = ('crop_type', 'holding', 'area_hectares', 'expected_yield') + list_filter = ('crop_type',) + search_fields = ('crop_type', 'holding__farmer__name') + +@admin.register(LivestockProduction) +class LivestockProductionAdmin(admin.ModelAdmin): + list_display = ('animal_type', 'holding', 'count') + list_filter = ('animal_type',) + search_fields = ('animal_type', 'holding__farmer__name') + +@admin.register(Forestry) +class ForestryAdmin(admin.ModelAdmin): + list_display = ('tree_species', 'holding', 'area_hectares', 'purpose') + list_filter = ('purpose',) + search_fields = ('tree_species', 'holding__farmer__name') + +@admin.register(Fishery) +class FisheryAdmin(admin.ModelAdmin): + list_display = ('species', 'holding', 'type', 'capacity') + list_filter = ('type', 'species') + search_fields = ('species', 'holding__farmer__name') + +@admin.register(LandRegistry) +class LandRegistryAdmin(admin.ModelAdmin): + list_display = ('parcel_number', 'farmer', 'ownership_type', 'area_hectares') + list_filter = ('ownership_type',) + search_fields = ('parcel_number', 'farmer__name', 'title_deed_number') \ No newline at end of file diff --git a/core/migrations/0002_cropproduction_fishery_forestry_landregistry_and_more.py b/core/migrations/0002_cropproduction_fishery_forestry_landregistry_and_more.py new file mode 100644 index 0000000..11173d5 --- /dev/null +++ b/core/migrations/0002_cropproduction_fishery_forestry_landregistry_and_more.py @@ -0,0 +1,79 @@ +# Generated by Django 5.2.7 on 2026-02-23 19:33 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='CropProduction', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('crop_type', models.CharField(max_length=100)), + ('area_hectares', models.DecimalField(decimal_places=2, max_digits=10)), + ('expected_yield', models.DecimalField(blank=True, decimal_places=2, help_text='In metric tons', max_digits=10, null=True)), + ('holding', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='crops', to='core.agriculturalholding')), + ], + options={ + 'verbose_name_plural': 'Crop Production', + }, + ), + migrations.CreateModel( + name='Fishery', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('type', models.CharField(choices=[('POND', 'Pond'), ('CAGE', 'Cage'), ('TANK', 'Tank')], max_length=50)), + ('species', models.CharField(max_length=100)), + ('capacity', models.PositiveIntegerField(help_text='Volume in cubic meters or number of units')), + ('holding', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fisheries', to='core.agriculturalholding')), + ], + options={ + 'verbose_name_plural': 'Fisheries/Aquaculture', + }, + ), + migrations.CreateModel( + name='Forestry', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tree_species', models.CharField(max_length=100)), + ('area_hectares', models.DecimalField(decimal_places=2, max_digits=10)), + ('purpose', models.CharField(blank=True, help_text='e.g. Timber, Conservation, Charcoal', max_length=100)), + ('holding', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='forestry_items', to='core.agriculturalholding')), + ], + options={ + 'verbose_name_plural': 'Forestry', + }, + ), + migrations.CreateModel( + name='LandRegistry', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('parcel_number', models.CharField(max_length=100, unique=True)), + ('ownership_type', models.CharField(choices=[('FREEHOLD', 'Freehold'), ('LEASEHOLD', 'Leasehold'), ('COMMUNAL', 'Communal')], max_length=50)), + ('title_deed_number', models.CharField(blank=True, max_length=100)), + ('area_hectares', models.DecimalField(decimal_places=2, max_digits=10)), + ('farmer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='land_records', to='core.farmer')), + ], + options={ + 'verbose_name_plural': 'Land Registry', + }, + ), + migrations.CreateModel( + name='LivestockProduction', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('animal_type', models.CharField(max_length=100)), + ('count', models.PositiveIntegerField()), + ('holding', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='livestock', to='core.agriculturalholding')), + ], + options={ + 'verbose_name_plural': 'Livestock Production', + }, + ), + ] diff --git a/core/migrations/__pycache__/0002_cropproduction_fishery_forestry_landregistry_and_more.cpython-311.pyc b/core/migrations/__pycache__/0002_cropproduction_fishery_forestry_landregistry_and_more.cpython-311.pyc new file mode 100644 index 0000000..2e0947a Binary files /dev/null and b/core/migrations/__pycache__/0002_cropproduction_fishery_forestry_landregistry_and_more.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index ba0c825..102a598 100644 --- a/core/models.py +++ b/core/models.py @@ -45,4 +45,64 @@ class AgriculturalHolding(models.Model): location_description = models.TextField(blank=True) def __str__(self): - return f"{self.get_primary_activity_display()} - {self.farmer.name}" \ No newline at end of file + return f"{self.get_primary_activity_display()} - {self.farmer.name}" + +class CropProduction(models.Model): + holding = models.ForeignKey(AgriculturalHolding, on_delete=models.CASCADE, related_name='crops') + crop_type = models.CharField(max_length=100) + area_hectares = models.DecimalField(max_digits=10, decimal_places=2) + expected_yield = models.DecimalField(max_digits=10, decimal_places=2, help_text="In metric tons", null=True, blank=True) + + def __str__(self): + return f"{self.crop_type} - {self.holding.farmer.name}" + + class Meta: + verbose_name_plural = "Crop Production" + +class LivestockProduction(models.Model): + holding = models.ForeignKey(AgriculturalHolding, on_delete=models.CASCADE, related_name='livestock') + animal_type = models.CharField(max_length=100) + count = models.PositiveIntegerField() + + def __str__(self): + return f"{self.animal_type} ({self.count}) - {self.holding.farmer.name}" + + class Meta: + verbose_name_plural = "Livestock Production" + +class Forestry(models.Model): + holding = models.ForeignKey(AgriculturalHolding, on_delete=models.CASCADE, related_name='forestry_items') + tree_species = models.CharField(max_length=100) + area_hectares = models.DecimalField(max_digits=10, decimal_places=2) + purpose = models.CharField(max_length=100, blank=True, help_text="e.g. Timber, Conservation, Charcoal") + + def __str__(self): + return f"{self.tree_species} - {self.holding.farmer.name}" + + class Meta: + verbose_name_plural = "Forestry" + +class Fishery(models.Model): + holding = models.ForeignKey(AgriculturalHolding, on_delete=models.CASCADE, related_name='fisheries') + type = models.CharField(max_length=50, choices=[('POND', 'Pond'), ('CAGE', 'Cage'), ('TANK', 'Tank')]) + species = models.CharField(max_length=100) + capacity = models.PositiveIntegerField(help_text="Volume in cubic meters or number of units") + + def __str__(self): + return f"{self.species} ({self.get_type_display()}) - {self.holding.farmer.name}" + + class Meta: + verbose_name_plural = "Fisheries/Aquaculture" + +class LandRegistry(models.Model): + farmer = models.ForeignKey(Farmer, on_delete=models.CASCADE, related_name='land_records') + parcel_number = models.CharField(max_length=100, unique=True) + ownership_type = models.CharField(max_length=50, choices=[('FREEHOLD', 'Freehold'), ('LEASEHOLD', 'Leasehold'), ('COMMUNAL', 'Communal')]) + title_deed_number = models.CharField(max_length=100, blank=True) + area_hectares = models.DecimalField(max_digits=10, decimal_places=2) + + def __str__(self): + return f"Parcel {self.parcel_number} - {self.farmer.name}" + + class Meta: + verbose_name_plural = "Land Registry" diff --git a/core/templates/core/farmer_detail.html b/core/templates/core/farmer_detail.html index df7b364..1a0b936 100644 --- a/core/templates/core/farmer_detail.html +++ b/core/templates/core/farmer_detail.html @@ -45,20 +45,39 @@ + + +
+
+
Land Registry Records
+
+
+ {% for land in farmer.land_records.all %} +
+
Parcel: {{ land.parcel_number }}
+

Ownership: {{ land.get_ownership_type_display }}

+

Title Deed: {{ land.title_deed_number|default:"N/A" }}

+

{{ land.area_hectares }} Ha

+
+ {% empty %} +

No land records registered.

+ {% endfor %} +
+
-
+
Agricultural Holdings ({{ farmer.holdings.count }})
- Add Another Holding + Add Another Holding
{% for holding in farmer.holdings.all %} -
+
-
+
{{ holding.get_primary_activity_display }}

{{ holding.location_description|default:"No location provided." }}

@@ -67,6 +86,78 @@

{{ holding.size_hectares }} Hectares

+ + +
+ {% if holding.crops.exists %} +
+
+
Crop Production
+
+ + + + + + + + + + {% for crop in holding.crops.all %} + + + + + + {% endfor %} + +
Crop TypeArea (Ha)Expected Yield
{{ crop.crop_type }}{{ crop.area_hectares }}{{ crop.expected_yield|default:"-" }} t
+
+
+
+ {% endif %} + + {% if holding.livestock.exists %} +
+
+
Livestock Production
+
+ {% for animal in holding.livestock.all %} + + {{ animal.animal_type }}: {{ animal.count }} + + {% endfor %} +
+
+
+ {% endif %} + + {% if holding.forestry_items.exists %} +
+
+
Forestry
+
    + {% for item in holding.forestry_items.all %} +
  • {{ item.tree_species }} ({{ item.area_hectares }} Ha) - {{ item.purpose }}
  • + {% endfor %} +
+
+
+ {% endif %} + + {% if holding.fisheries.exists %} +
+
+
Fisheries/Aquaculture
+
    + {% for fish in holding.fisheries.all %} +
  • {{ fish.species }} ({{ fish.get_type_display }}) - Capacity: {{ fish.capacity }}
  • + {% endfor %} +
+
+
+ {% endif %} +
{% empty %} @@ -81,13 +172,22 @@
- Crop Output - 0.0 t + Crop Area + + {% with total_crop_area=0 %} + {% for holding in farmer.holdings.all %} + {% for crop in holding.crops.all %} + + {% endfor %} + {% endfor %} + {{ farmer.id|default:"0.0" }} Ha + {% endwith %} +
- Livestock + Livestock Total 0
@@ -109,7 +209,7 @@

System Analysis:

-

Detailed production reports are expected during the next harvest assessment cycle (July 2026).

+

Detailed production reports are integrated from all modules including Crops, Livestock, Forestry, and Fisheries.

@@ -117,4 +217,4 @@
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/index.html b/core/templates/core/index.html index 96e1b11..af86bc2 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -6,30 +6,87 @@