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 @@ + + +
Ownership: {{ land.get_ownership_type_display }}
+Title Deed: {{ land.title_deed_number|default:"N/A" }}
+{{ land.area_hectares }} Ha
+No land records registered.
+ {% endfor %} +{{ holding.location_description|default:"No location provided." }}
@@ -67,6 +86,78 @@| Crop Type | +Area (Ha) | +Expected Yield | +
|---|---|---|
| {{ crop.crop_type }} | +{{ crop.area_hectares }} | +{{ crop.expected_yield|default:"-" }} t | +
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.