8 changed files with 228015 additions and 493 deletions
-
175apps/account/management/commands/create_geonames_table.py
-
4apps/account/views/user.py
-
633apps/hadis/management/commands/seed_category_data.py
-
6city_detection_ip.py
-
4entrypoint.sh
-
103test_mixmind_coordinates.py
-
227583utils/geonames_data/cities500.txt
-
BINutils/geonames_data/cities500.zip
@ -0,0 +1,175 @@ |
|||||
|
import os |
||||
|
import csv |
||||
|
import zipfile |
||||
|
import requests |
||||
|
from pathlib import Path |
||||
|
from django.core.management.base import BaseCommand, CommandError |
||||
|
from django.db import connection |
||||
|
|
||||
|
|
||||
|
class Command(BaseCommand): |
||||
|
help = 'Create and populate geonames_city table with GeoNames data' |
||||
|
|
||||
|
def add_arguments(self, parser): |
||||
|
parser.add_argument( |
||||
|
'--force', |
||||
|
action='store_true', |
||||
|
help='Force recreation of table even if it exists', |
||||
|
) |
||||
|
parser.add_argument( |
||||
|
'--skip-download', |
||||
|
action='store_true', |
||||
|
help='Skip downloading data, use existing files', |
||||
|
) |
||||
|
|
||||
|
def handle(self, *args, **options): |
||||
|
self.stdout.write('Creating geonames_city table...') |
||||
|
|
||||
|
# Create table |
||||
|
with connection.cursor() as cursor: |
||||
|
if options['force']: |
||||
|
cursor.execute('DROP TABLE IF EXISTS geonames_city') |
||||
|
|
||||
|
cursor.execute(''' |
||||
|
CREATE TABLE IF NOT EXISTS geonames_city ( |
||||
|
id SERIAL PRIMARY KEY, |
||||
|
geonameid INTEGER, |
||||
|
name VARCHAR(200), |
||||
|
asciiname VARCHAR(200), |
||||
|
alternatenames TEXT, |
||||
|
latitude DECIMAL(10, 7), |
||||
|
longitude DECIMAL(10, 7), |
||||
|
feature_class CHAR(1), |
||||
|
feature_code VARCHAR(10), |
||||
|
country_code CHAR(2), |
||||
|
cc2 VARCHAR(200), |
||||
|
admin1_code VARCHAR(20), |
||||
|
admin2_code VARCHAR(80), |
||||
|
admin3_code VARCHAR(20), |
||||
|
admin4_code VARCHAR(20), |
||||
|
population BIGINT, |
||||
|
elevation INTEGER, |
||||
|
dem INTEGER, |
||||
|
timezone VARCHAR(40), |
||||
|
modification_date DATE |
||||
|
) |
||||
|
''') |
||||
|
|
||||
|
# Create indexes for better performance |
||||
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_geonames_city_coords ON geonames_city (latitude, longitude)') |
||||
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_geonames_city_country ON geonames_city (country_code)') |
||||
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_geonames_city_feature ON geonames_city (feature_class)') |
||||
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_geonames_city_population ON geonames_city (population)') |
||||
|
|
||||
|
self.stdout.write(self.style.SUCCESS('Table created successfully')) |
||||
|
|
||||
|
if not options['skip_download']: |
||||
|
self.download_and_import_data() |
||||
|
else: |
||||
|
self.stdout.write('Skipping download, using existing data...') |
||||
|
|
||||
|
def download_and_import_data(self): |
||||
|
"""Download and import GeoNames cities data""" |
||||
|
self.stdout.write('Downloading GeoNames cities data...') |
||||
|
|
||||
|
# Create data directory |
||||
|
data_dir = Path('utils/geonames_data') |
||||
|
data_dir.mkdir(exist_ok=True) |
||||
|
|
||||
|
# Download cities500.zip (cities with population > 500) |
||||
|
url = 'https://download.geonames.org/export/dump/cities500.zip' |
||||
|
zip_path = data_dir / 'cities500.zip' |
||||
|
|
||||
|
try: |
||||
|
response = requests.get(url, stream=True) |
||||
|
response.raise_for_status() |
||||
|
|
||||
|
with open(zip_path, 'wb') as f: |
||||
|
for chunk in response.iter_content(chunk_size=8192): |
||||
|
f.write(chunk) |
||||
|
|
||||
|
self.stdout.write('Download completed') |
||||
|
|
||||
|
# Extract zip file |
||||
|
with zipfile.ZipFile(zip_path, 'r') as zip_ref: |
||||
|
zip_ref.extractall(data_dir) |
||||
|
|
||||
|
# Import data |
||||
|
self.import_cities_data(data_dir / 'cities500.txt') |
||||
|
|
||||
|
except Exception as e: |
||||
|
raise CommandError(f'Failed to download/import data: {e}') |
||||
|
|
||||
|
def import_cities_data(self, txt_file): |
||||
|
"""Import cities data from GeoNames text file""" |
||||
|
self.stdout.write(f'Importing data from {txt_file}...') |
||||
|
|
||||
|
if not txt_file.exists(): |
||||
|
raise CommandError(f'File {txt_file} does not exist') |
||||
|
|
||||
|
batch_size = 1000 |
||||
|
batch = [] |
||||
|
|
||||
|
with open(txt_file, 'r', encoding='utf-8') as f: |
||||
|
for line_num, line in enumerate(f, 1): |
||||
|
if line_num % 10000 == 0: |
||||
|
self.stdout.write(f'Processing line {line_num}...') |
||||
|
|
||||
|
fields = line.strip().split('\t') |
||||
|
if len(fields) < 19: |
||||
|
continue |
||||
|
|
||||
|
try: |
||||
|
# Parse the GeoNames format |
||||
|
geonameid = int(fields[0]) |
||||
|
name = fields[1][:200] if fields[1] else '' |
||||
|
asciiname = fields[2][:200] if fields[2] else '' |
||||
|
alternatenames = fields[3] if fields[3] else '' |
||||
|
latitude = float(fields[4]) |
||||
|
longitude = float(fields[5]) |
||||
|
feature_class = fields[6] |
||||
|
feature_code = fields[7] |
||||
|
country_code = fields[8][:2] if fields[8] else '' |
||||
|
cc2 = fields[9] if fields[9] else '' |
||||
|
admin1_code = fields[10] if fields[10] else '' |
||||
|
admin2_code = fields[11] if fields[11] else '' |
||||
|
admin3_code = fields[12] if fields[12] else '' |
||||
|
admin4_code = fields[13] if fields[13] else '' |
||||
|
population = int(fields[14]) if fields[14] and fields[14] != '0' else 0 |
||||
|
elevation = int(fields[15]) if fields[15] else None |
||||
|
dem = int(fields[16]) if fields[16] else None |
||||
|
timezone = fields[17] if fields[17] else '' |
||||
|
modification_date = fields[18] if fields[18] else None |
||||
|
|
||||
|
batch.append(( |
||||
|
geonameid, name, asciiname, alternatenames, latitude, longitude, |
||||
|
feature_class, feature_code, country_code, cc2, admin1_code, |
||||
|
admin2_code, admin3_code, admin4_code, population, elevation, |
||||
|
dem, timezone, modification_date |
||||
|
)) |
||||
|
|
||||
|
if len(batch) >= batch_size: |
||||
|
self.insert_batch(batch) |
||||
|
batch = [] |
||||
|
|
||||
|
except (ValueError, IndexError) as e: |
||||
|
self.stdout.write(self.style.WARNING(f'Error parsing line {line_num}: {e}')) |
||||
|
continue |
||||
|
|
||||
|
# Insert remaining records |
||||
|
if batch: |
||||
|
self.insert_batch(batch) |
||||
|
|
||||
|
self.stdout.write(self.style.SUCCESS('Data import completed')) |
||||
|
|
||||
|
def insert_batch(self, batch): |
||||
|
"""Insert a batch of records into the database""" |
||||
|
with connection.cursor() as cursor: |
||||
|
cursor.executemany(''' |
||||
|
INSERT INTO geonames_city ( |
||||
|
geonameid, name, asciiname, alternatenames, latitude, longitude, |
||||
|
feature_class, feature_code, country_code, cc2, admin1_code, |
||||
|
admin2_code, admin3_code, admin4_code, population, elevation, |
||||
|
dem, timezone, modification_date |
||||
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) |
||||
|
''', batch) |
||||
@ -1,497 +1,154 @@ |
|||||
""" |
|
||||
Django management command to seed mock HadisCategory trees and new Hadis |
|
||||
for history, fatwa and quote sources. |
|
||||
|
|
||||
Place this file in: hadis/management/commands/seed_categories.py |
|
||||
|
|
||||
Usage: |
|
||||
python manage.py seed_categories |
|
||||
python manage.py seed_categories --clear |
|
||||
""" |
|
||||
|
|
||||
|
import random |
||||
from django.core.management.base import BaseCommand |
from django.core.management.base import BaseCommand |
||||
from django.utils.text import slugify |
|
||||
from django.conf import settings |
|
||||
|
|
||||
from apps.hadis.models.category import HadisCategory, HadisSect |
|
||||
from apps.hadis.models.hadis import Hadis, HadisStatus, HadisTag |
|
||||
|
|
||||
|
from django.db import transaction |
||||
|
# Adjust the import path if your app name is different |
||||
|
from apps.hadis.models import Hadis, HadisCategory, HadisSect |
||||
|
|
||||
class Command(BaseCommand): |
class Command(BaseCommand): |
||||
help = "Seed HadisCategory trees (history, fatwa, quote) and new Hadis connected to them." |
|
||||
|
|
||||
def add_arguments(self, parser): |
|
||||
parser.add_argument( |
|
||||
'--clear', |
|
||||
action='store_true', |
|
||||
help='Clear ONLY categories & hadises created by this command (history/fatwa/quote).', |
|
||||
) |
|
||||
|
help = 'Seeds HadisCategories with rich Islamic data and links existing Hadiths' |
||||
|
|
||||
def handle(self, *args, **options): |
def handle(self, *args, **options): |
||||
if options['clear']: |
|
||||
self._clear_seeded_data() |
|
||||
|
|
||||
# Ensure sects exist |
|
||||
try: |
|
||||
shia_sect = HadisSect.objects.get(sect_type=HadisSect.SectType.SHIA) |
|
||||
except HadisSect.DoesNotExist: |
|
||||
shia_sect = HadisSect.objects.create( |
|
||||
sect_type=HadisSect.SectType.SHIA, |
|
||||
title="Shia", |
|
||||
description="Default Shia sect" |
|
||||
) |
|
||||
|
|
||||
try: |
|
||||
sunni_sect = HadisSect.objects.get(sect_type=HadisSect.SectType.SUNNI) |
|
||||
except HadisSect.DoesNotExist: |
|
||||
sunni_sect = HadisSect.objects.create( |
|
||||
sect_type=HadisSect.SectType.SUNNI, |
|
||||
title="Sunni", |
|
||||
description="Default Sunni sect" |
|
||||
) |
|
||||
|
|
||||
# Optional: ensure a couple of HadisStatus entries exist |
|
||||
default_status, _ = HadisStatus.objects.get_or_create( |
|
||||
title="Authentic / Accepted", |
|
||||
defaults={"color": HadisStatus.ColorChoices.GREEN, "order": 1}, |
|
||||
) |
|
||||
weak_status, _ = HadisStatus.objects.get_or_create( |
|
||||
title="Weak / Needs Review", |
|
||||
defaults={"color": HadisStatus.ColorChoices.YELLOW, "order": 2}, |
|
||||
) |
|
||||
|
|
||||
# Ensure some tags exist |
|
||||
tags = self._ensure_tags() |
|
||||
|
|
||||
# 1) Seed HISTORY categories & hadises |
|
||||
self.stdout.write(self.style.SUCCESS("\n📚 Creating HISTORY categories and hadiths...")) |
|
||||
self._seed_history_tree(sunni_sect, default_status, tags) |
|
||||
|
|
||||
# 2) Seed FATWA categories & hadises |
|
||||
self.stdout.write(self.style.SUCCESS("\n📜 Creating FATWA categories and hadiths...")) |
|
||||
self._seed_fatwa_tree(sunni_sect, default_status, weak_status, tags) |
|
||||
|
|
||||
# 3) Seed QUOTE categories & hadises |
|
||||
self.stdout.write(self.style.SUCCESS("\n💬 Creating QUOTE categories and hadiths...")) |
|
||||
self._seed_quote_tree(shia_sect, default_status, tags) |
|
||||
|
|
||||
self._print_summary() |
|
||||
|
|
||||
# ------------------------------------------------------------------ # |
|
||||
# Helpers |
|
||||
# ------------------------------------------------------------------ # |
|
||||
|
|
||||
def _clear_seeded_data(self): |
|
||||
""" |
|
||||
Clear only categories with source_type in (history, fatwa, quote) |
|
||||
and hadises that are attached to those categories. |
|
||||
""" |
|
||||
self.stdout.write(self.style.WARNING("Clearing previously seeded history/fatwa/quote data...")) |
|
||||
leaf_categories = HadisCategory.objects.filter( |
|
||||
source_type__in=[ |
|
||||
HadisCategory.SourceType.HISTORY, |
|
||||
HadisCategory.SourceType.FATWA, |
|
||||
HadisCategory.SourceType.QUOTE, |
|
||||
] |
|
||||
) |
|
||||
Hadis.objects.filter(category__in=leaf_categories).delete() |
|
||||
# Delete categories (MPTT will handle tree structure) |
|
||||
HadisCategory.objects.filter( |
|
||||
source_type__in=[ |
|
||||
HadisCategory.SourceType.HISTORY, |
|
||||
HadisCategory.SourceType.FATWA, |
|
||||
HadisCategory.SourceType.QUOTE, |
|
||||
] |
|
||||
).delete() |
|
||||
self.stdout.write(self.style.SUCCESS("✓ Cleared seeded history/fatwa/quote categories and hadiths.")) |
|
||||
|
|
||||
def _ensure_tags(self): |
|
||||
base_titles = [ |
|
||||
"history", |
|
||||
"biography", |
|
||||
"battle", |
|
||||
"ethics", |
|
||||
"jurisprudence", |
|
||||
"family", |
|
||||
"wisdom", |
|
||||
"short quote", |
|
||||
] |
|
||||
tags = [] |
|
||||
for title in base_titles: |
|
||||
tag, _ = HadisTag.objects.get_or_create(title=title) |
|
||||
tags.append(tag) |
|
||||
return tags |
|
||||
|
|
||||
# ---------------- HISTORY ---------------- # |
|
||||
|
|
||||
def _seed_history_tree(self, sect, default_status, tags): |
|
||||
""" |
|
||||
Create a small HISTORY tree: |
|
||||
History of Islam |
|
||||
├─ Early Caliphate (leaf) |
|
||||
└─ Battles (leaf) |
|
||||
And add several hadith-like historical reports. |
|
||||
""" |
|
||||
root, _ = HadisCategory.objects.get_or_create( |
|
||||
sect=sect, |
|
||||
source_type=HadisCategory.SourceType.HISTORY, |
|
||||
title="History of Islam", |
|
||||
defaults={ |
|
||||
"description": "High-level historical themes related to early Islamic history.", |
|
||||
"order": 1, |
|
||||
}, |
|
||||
) |
|
||||
|
|
||||
early_caliphate, _ = HadisCategory.objects.get_or_create( |
|
||||
sect=sect, |
|
||||
source_type=HadisCategory.SourceType.HISTORY, |
|
||||
parent=root, |
|
||||
title="Early Caliphate", |
|
||||
defaults={ |
|
||||
"description": "Events and reports from the period of the first caliphs.", |
|
||||
"order": 1, |
|
||||
|
self.stdout.write(self.style.WARNING('--- Starting Islamic Category Seeding ---')) |
||||
|
|
||||
|
# 1. Fetch or Create Sects |
||||
|
shia_sect, _ = HadisSect.objects.get_or_create( |
||||
|
sect_type=HadisSect.SectType.SHIA, |
||||
|
defaults={'title': [{'text': 'Shia (Twelver)', 'language_code': 'en'}], 'is_active': True} |
||||
|
) |
||||
|
sunni_sect, _ = HadisSect.objects.get_or_create( |
||||
|
sect_type=HadisSect.SectType.SUNNI, |
||||
|
defaults={'title': [{'text': 'Sunni (Ahlus Sunnah)', 'language_code': 'en'}], 'is_active': True} |
||||
|
) |
||||
|
|
||||
|
# 2. Rich Islamic Data Dictionary |
||||
|
ISLAMIC_DATA = { |
||||
|
HadisCategory.SourceType.QURAN: { |
||||
|
HadisSect.SectType.SHIA: [ |
||||
|
("Quranic Sciences", "Thematic Exegesis", ["Verses of Wilayah", "Ahlulbayt in Quran", "Moral Teachings in Surah Yusuf"]), |
||||
|
("Tafsir Studies", "Interpretation Principles", ["Tafsir al-Mizan Topics", "Esoteric Meanings (Ta'wil)"]) |
||||
|
], |
||||
|
HadisSect.SectType.SUNNI: [ |
||||
|
("Quranic Studies", "Tafsir Methodology", ["Asbab al-Nuzul", "Stories of the Prophets", "Legislative Verses (Ahkam)"]), |
||||
|
("Recitation (Tajweed)", "Qira'at", ["Hafs an Asim", "Warsh an Nafi", "Rules of Nun Sakinah"]) |
||||
|
] |
||||
}, |
}, |
||||
) |
|
||||
|
|
||||
battles, _ = HadisCategory.objects.get_or_create( |
|
||||
sect=sect, |
|
||||
source_type=HadisCategory.SourceType.HISTORY, |
|
||||
parent=root, |
|
||||
title="Battles and Expeditions", |
|
||||
defaults={ |
|
||||
"description": "Key battles and expeditions in early Islamic history.", |
|
||||
"order": 2, |
|
||||
}, |
|
||||
) |
|
||||
|
|
||||
# Create some historical “hadith” entries (reports) |
|
||||
# For simplicity, use new numbers starting from 1900+ |
|
||||
self._create_hadis( |
|
||||
number=1900, |
|
||||
category=early_caliphate, |
|
||||
title="The Consultation of the Companions", |
|
||||
text=( |
|
||||
"It is reported in historical works that the companions would gather and " |
|
||||
"consult one another regarding major affairs of the community." |
|
||||
), |
|
||||
translation_en=( |
|
||||
"Historical reports mention that the companions would consult one another " |
|
||||
"on major communal matters." |
|
||||
), |
|
||||
status=default_status, |
|
||||
tags=[t for t in tags if t.title in ["history", "biography"]], |
|
||||
) |
|
||||
|
|
||||
self._create_hadis( |
|
||||
number=1901, |
|
||||
category=early_caliphate, |
|
||||
title="Establishment of the Public Treasury", |
|
||||
text=( |
|
||||
"Some historians narrate that during the early caliphate a public treasury " |
|
||||
"was organized to administer charity and public funds." |
|
||||
), |
|
||||
translation_en=( |
|
||||
"Historical sources state that a public treasury was set up to manage " |
|
||||
"charity and communal funds." |
|
||||
), |
|
||||
status=default_status, |
|
||||
tags=[t for t in tags if t.title in ["history", "jurisprudence"]], |
|
||||
) |
|
||||
|
|
||||
self._create_hadis( |
|
||||
number=1910, |
|
||||
category=battles, |
|
||||
title="Report of the Battle Preparations", |
|
||||
text=( |
|
||||
"Chronicles record that the believers prepared carefully before major battles, " |
|
||||
"ensuring justice and discipline in their ranks." |
|
||||
), |
|
||||
translation_en=( |
|
||||
"Historical chronicles record careful preparations and discipline before major battles." |
|
||||
), |
|
||||
status=default_status, |
|
||||
tags=[t for t in tags if t.title in ["history", "battle"]], |
|
||||
) |
|
||||
|
|
||||
self._create_hadis( |
|
||||
number=1911, |
|
||||
category=battles, |
|
||||
title="Mercy Shown After Victory", |
|
||||
text=( |
|
||||
"Historical narrations mention that after some victories, clemency and mercy were " |
|
||||
"shown to prisoners and civilians." |
|
||||
), |
|
||||
translation_en=( |
|
||||
"Historical narrations mention mercy and clemency after some military victories." |
|
||||
), |
|
||||
status=default_status, |
|
||||
tags=[t for t in tags if t.title in ["history", "ethics"]], |
|
||||
) |
|
||||
|
|
||||
# ---------------- FATWA ---------------- # |
|
||||
|
|
||||
def _seed_fatwa_tree(self, sect, default_status, weak_status, tags): |
|
||||
""" |
|
||||
Create a FATWA tree like: |
|
||||
Contemporary Fatwas |
|
||||
├─ Worship (leaf) |
|
||||
└─ Family Issues (leaf) |
|
||||
Plus a second root: |
|
||||
Financial Fatwas |
|
||||
└─ Trade and Contracts (leaf) |
|
||||
""" |
|
||||
root_contemporary, _ = HadisCategory.objects.get_or_create( |
|
||||
sect=sect, |
|
||||
source_type=HadisCategory.SourceType.FATWA, |
|
||||
title="Contemporary Fatwas", |
|
||||
defaults={ |
|
||||
"description": "Modern juristic responses to contemporary questions.", |
|
||||
"order": 1, |
|
||||
|
HadisCategory.SourceType.HADITH: { |
||||
|
HadisSect.SectType.SHIA: [ |
||||
|
("The Four Books", "Usul al-Kafi", ["Book of Intellect", "Book of Divine Proof", "Book of Belief"]), |
||||
|
("Nahj al-Balagha", "Sermons of Imam Ali", ["The Shiqshiqiyyah Sermon", "Letter to Malik al-Ashtar", "Aphorisms of Wisdom"]) |
||||
|
], |
||||
|
HadisSect.SectType.SUNNI: [ |
||||
|
("The Six Books", "Sahih al-Bukhari", ["Book of Revelation", "Book of Belief", "Book of Knowledge"]), |
||||
|
("Sunan Collections", "Sunan Abu Dawood", ["Book of Prayer", "Book of Zakat", "Book of Jihad"]) |
||||
|
] |
||||
}, |
}, |
||||
) |
|
||||
|
|
||||
worship, _ = HadisCategory.objects.get_or_create( |
|
||||
sect=sect, |
|
||||
source_type=HadisCategory.SourceType.FATWA, |
|
||||
parent=root_contemporary, |
|
||||
title="Worship and Rituals", |
|
||||
defaults={ |
|
||||
"description": "Fatwas about prayer, fasting and other acts of worship.", |
|
||||
"order": 1, |
|
||||
|
HadisCategory.SourceType.HISTORY: { |
||||
|
HadisSect.SectType.SHIA: [ |
||||
|
("Life of the Infallibles", "The Tragedy of Karbala", ["Day of Ashura", "The Sermon of Zaynab (SA)", "Journey of Captives"]), |
||||
|
("Occultation (Ghaybah)", "Major Occultation", ["Signs of Reappearance", "Duties of Believers", "Deputies of the Imam"]) |
||||
|
], |
||||
|
HadisSect.SectType.SUNNI: [ |
||||
|
("Seerah of the Prophet", "Meccan Period", ["The First Revelation", "Migration to Abyssinia", "The Year of Sorrow"]), |
||||
|
("The Rashidun Caliphate", "Conquests and Expansion", ["Caliphate of Abu Bakr", "Caliphate of Umar", "Battles of Yarmouk"]) |
||||
|
] |
||||
}, |
}, |
||||
) |
|
||||
|
|
||||
family_issues, _ = HadisCategory.objects.get_or_create( |
|
||||
sect=sect, |
|
||||
source_type=HadisCategory.SourceType.FATWA, |
|
||||
parent=root_contemporary, |
|
||||
title="Family Issues", |
|
||||
defaults={ |
|
||||
"description": "Fatwas regarding marriage, divorce and family obligations.", |
|
||||
"order": 2, |
|
||||
|
HadisCategory.SourceType.FATWA: { |
||||
|
HadisSect.SectType.SHIA: [ |
||||
|
("Jurisprudence (Fiqh)", "Acts of Worship", ["Rules of Taqlid", "Khums and Zakat", "Rules of Salatul Layl"]), |
||||
|
("Modern Legal Issues", "Medical Jurisprudence", ["Organ Donation", "Assisted Reproduction", "Gender Reassignment"]) |
||||
|
], |
||||
|
HadisSect.SectType.SUNNI: [ |
||||
|
("Fiqh Schools", "Shafi'i School", ["Rules of Prayer", "Inheritance Laws", "Marriage Contracts"]), |
||||
|
("Contemporary Fatawa", "Islamic Finance", ["Prohibition of Riba", "Islamic Banking Principles", "Crypto-currency Rulings"]) |
||||
|
] |
||||
}, |
}, |
||||
) |
|
||||
|
|
||||
financial_root, _ = HadisCategory.objects.get_or_create( |
|
||||
sect=sect, |
|
||||
source_type=HadisCategory.SourceType.FATWA, |
|
||||
title="Financial Fatwas", |
|
||||
defaults={ |
|
||||
"description": "Juristic rulings about trade, contracts and modern finance.", |
|
||||
"order": 2, |
|
||||
}, |
|
||||
) |
|
||||
|
|
||||
trade_contracts, _ = HadisCategory.objects.get_or_create( |
|
||||
sect=sect, |
|
||||
source_type=HadisCategory.SourceType.FATWA, |
|
||||
parent=financial_root, |
|
||||
title="Trade and Contracts", |
|
||||
defaults={ |
|
||||
"description": "Fatwas related to buying, selling and contractual agreements.", |
|
||||
"order": 1, |
|
||||
}, |
|
||||
) |
|
||||
|
|
||||
# Create some “fatwa-style” hadith entries (answers / rulings) |
|
||||
self._create_hadis( |
|
||||
number=1920, |
|
||||
category=worship, |
|
||||
title="Fatwa on Combining Prayers While Traveling", |
|
||||
text=( |
|
||||
"A contemporary juristic council has ruled that combining prayers while traveling " |
|
||||
"is permitted when hardship is present, following classical precedents." |
|
||||
), |
|
||||
translation_en=( |
|
||||
"A modern fatwa allows combining prayers during travel where hardship exists, " |
|
||||
"based on earlier jurisprudence." |
|
||||
), |
|
||||
status=default_status, |
|
||||
tags=[t for t in tags if t.title in ["jurisprudence", "ethics"]], |
|
||||
) |
|
||||
|
|
||||
self._create_hadis( |
|
||||
number=1921, |
|
||||
category=worship, |
|
||||
title="Fatwa on Using Local Calculations for Prayer Times", |
|
||||
text=( |
|
||||
"Modern scholars have issued fatwas permitting the use of accurate astronomical " |
|
||||
"calculations for determining prayer times." |
|
||||
), |
|
||||
translation_en=( |
|
||||
"Contemporary fatwas permit using precise astronomical calculations for prayer times." |
|
||||
), |
|
||||
status=weak_status, |
|
||||
tags=[t for t in tags if t.title in ["jurisprudence", "knowledge"]], |
|
||||
) |
|
||||
|
|
||||
self._create_hadis( |
|
||||
number=1930, |
|
||||
category=family_issues, |
|
||||
title="Fatwa on Upholding Family Ties", |
|
||||
text=( |
|
||||
"A fatwa committee emphasized that maintaining family ties is obligatory and that " |
|
||||
"cutting off relatives without valid reason is sinful." |
|
||||
), |
|
||||
translation_en=( |
|
||||
"A fatwa stresses that keeping family ties is obligatory and severing them without cause is sinful." |
|
||||
), |
|
||||
status=default_status, |
|
||||
tags=[t for t in tags if t.title in ["family", "ethics"]], |
|
||||
) |
|
||||
|
|
||||
self._create_hadis( |
|
||||
number=1940, |
|
||||
category=trade_contracts, |
|
||||
title="Fatwa on Transparent Business Contracts", |
|
||||
text=( |
|
||||
"Scholars have ruled that contracts must be transparent and free from deception in order " |
|
||||
"to be valid in Islamic law." |
|
||||
), |
|
||||
translation_en=( |
|
||||
"A modern fatwa requires transparency and absence of deception in business contracts." |
|
||||
), |
|
||||
status=default_status, |
|
||||
tags=[t for t in tags if t.title in ["business", "jurisprudence"]] if any( |
|
||||
t.title == "business" for t in tags |
|
||||
) else [t for t in tags if t.title in ["jurisprudence"]], |
|
||||
) |
|
||||
|
|
||||
# ---------------- QUOTE ---------------- # |
|
||||
|
|
||||
def _seed_quote_tree(self, sect, default_status, tags): |
|
||||
""" |
|
||||
Create a QUOTE tree: |
|
||||
Wisdom Quotes |
|
||||
├─ Short Wisdom (leaf) |
|
||||
└─ On Knowledge (leaf) |
|
||||
""" |
|
||||
root, _ = HadisCategory.objects.get_or_create( |
|
||||
sect=sect, |
|
||||
source_type=HadisCategory.SourceType.QUOTE, |
|
||||
title="Wisdom Quotes", |
|
||||
defaults={ |
|
||||
"description": "Short wise sayings and inspirational quotes.", |
|
||||
"order": 1, |
|
||||
}, |
|
||||
) |
|
||||
|
|
||||
short_wisdom, _ = HadisCategory.objects.get_or_create( |
|
||||
sect=sect, |
|
||||
source_type=HadisCategory.SourceType.QUOTE, |
|
||||
parent=root, |
|
||||
title="Short Wisdom", |
|
||||
defaults={ |
|
||||
"description": "Very short, memorable quotes on character and behavior.", |
|
||||
"order": 1, |
|
||||
}, |
|
||||
) |
|
||||
|
|
||||
on_knowledge, _ = HadisCategory.objects.get_or_create( |
|
||||
sect=sect, |
|
||||
source_type=HadisCategory.SourceType.QUOTE, |
|
||||
parent=root, |
|
||||
title="On Knowledge", |
|
||||
defaults={ |
|
||||
"description": "Quotes emphasizing the virtue of knowledge and learning.", |
|
||||
"order": 2, |
|
||||
}, |
|
||||
) |
|
||||
|
|
||||
# Short quote-style entries |
|
||||
self._create_hadis( |
|
||||
number=1950, |
|
||||
category=short_wisdom, |
|
||||
title="The Measure of a Heart", |
|
||||
text="It is said: The worth of a person is in what their heart carries of mercy and truth.", |
|
||||
translation_en="It is said: A person’s value is measured by the mercy and truth in their heart.", |
|
||||
status=default_status, |
|
||||
tags=[t for t in tags if t.title in ["wisdom", "short quote", "ethics"]], |
|
||||
) |
|
||||
|
|
||||
self._create_hadis( |
|
||||
number=1951, |
|
||||
category=short_wisdom, |
|
||||
title="Silence and Wisdom", |
|
||||
text="One of the wise said: Many people would be considered wise if they knew when to remain silent.", |
|
||||
translation_en="A wise saying: Many would be counted wise if they knew when to be silent.", |
|
||||
status=default_status, |
|
||||
tags=[t for t in tags if t.title in ["wisdom", "short quote"]], |
|
||||
) |
|
||||
|
|
||||
self._create_hadis( |
|
||||
number=1960, |
|
||||
category=on_knowledge, |
|
||||
title="Seeking Knowledge as Light", |
|
||||
text="It is narrated from the scholars: Knowledge is a light that guides the heart towards what benefits it.", |
|
||||
translation_en="Scholars say: Knowledge is a light guiding the heart to what benefits it.", |
|
||||
status=default_status, |
|
||||
tags=[t for t in tags if t.title in ["knowledge", "wisdom"]], |
|
||||
) |
|
||||
|
|
||||
self._create_hadis( |
|
||||
number=1961, |
|
||||
category=on_knowledge, |
|
||||
title="Learning until the End", |
|
||||
text="One sage said: Continue to seek knowledge until the last day of your life, for ignorance is a darkness.", |
|
||||
translation_en="A sage said: Seek knowledge until your last day, for ignorance is darkness.", |
|
||||
status=default_status, |
|
||||
tags=[t for t in tags if t.title in ["knowledge", "wisdom"]], |
|
||||
) |
|
||||
|
|
||||
# ---------------- utility to create Hadis ---------------- # |
|
||||
|
|
||||
def _create_hadis(self, number, category, title, text, translation_en, status, tags): |
|
||||
""" |
|
||||
Create or update a Hadis with the given number and category. |
|
||||
Translation uses {'en': '...'} structure as requested. |
|
||||
""" |
|
||||
hadis, created = Hadis.objects.get_or_create( |
|
||||
number=number, |
|
||||
category=category, |
|
||||
defaults={ |
|
||||
"title": title, |
|
||||
"title_narrator": None, |
|
||||
"description": "", |
|
||||
"text": text, |
|
||||
"translation": {"en": translation_en}, |
|
||||
"status": True, |
|
||||
"hadis_status": status, |
|
||||
"hadis_status_text": status.title if status else "", |
|
||||
"address": "", |
|
||||
"links": {}, |
|
||||
"explanation": "", |
|
||||
}, |
|
||||
) |
|
||||
if not created: |
|
||||
hadis.title = title |
|
||||
hadis.text = text |
|
||||
hadis.translation = {"en": translation_en} |
|
||||
hadis.hadis_status = status |
|
||||
hadis.hadis_status_text = status.title if status else "" |
|
||||
hadis.category = category |
|
||||
hadis.save() |
|
||||
|
|
||||
if tags: |
|
||||
hadis.tags.set(tags) |
|
||||
return hadis |
|
||||
|
|
||||
def _print_summary(self): |
|
||||
from apps.hadis.models import HadisCategory, Hadis |
|
||||
|
|
||||
self.stdout.write("\n" + "=" * 70) |
|
||||
self.stdout.write(self.style.SUCCESS("CATEGORY & HADIS SUMMARY")) |
|
||||
self.stdout.write("=" * 70) |
|
||||
|
|
||||
history_count = HadisCategory.objects.filter(source_type=HadisCategory.SourceType.HISTORY).count() |
|
||||
fatwa_count = HadisCategory.objects.filter(source_type=HadisCategory.SourceType.FATWA).count() |
|
||||
quote_count = HadisCategory.objects.filter(source_type=HadisCategory.SourceType.QUOTE).count() |
|
||||
|
|
||||
history_hadis = Hadis.objects.filter(category__source_type=HadisCategory.SourceType.HISTORY).count() |
|
||||
fatwa_hadis = Hadis.objects.filter(category__source_type=HadisCategory.SourceType.FATWA).count() |
|
||||
quote_hadis = Hadis.objects.filter(category__source_type=HadisCategory.SourceType.QUOTE).count() |
|
||||
|
|
||||
self.stdout.write(f"📂 HISTORY categories: {history_count} | hadith-like records: {history_hadis}") |
|
||||
self.stdout.write(f"📂 FATWA categories: {fatwa_count} | fatwa records: {fatwa_hadis}") |
|
||||
self.stdout.write(f"📂 QUOTE categories: {quote_count} | quote records: {quote_hadis}") |
|
||||
self.stdout.write("=" * 70 + "\n") |
|
||||
|
HadisCategory.SourceType.QUOTE: { |
||||
|
HadisSect.SectType.SHIA: [ |
||||
|
("Wisdom of the Imams", "Supplications (Du'a)", ["Du'a Kumayl Themes", "Whispered Prayers (Munajat)", "Du'a Arafah"]), |
||||
|
("Mysticism (Irfan)", "Spiritual Wayfaring", ["Combat with the Self", "Degrees of Piety", "Love for the Divine"]) |
||||
|
], |
||||
|
HadisSect.SectType.SUNNI: [ |
||||
|
("Sayings of the Companions", "Wisdom of Abu Bakr & Umar", ["Justice in Governance", "Fear of Allah", "Humility"]), |
||||
|
("Sufi Wisdom", "Spiritual Purification", ["Tazkiyat al-Nafs", "Remembrance of Death", "Reliance on Allah (Tawakkul)"]) |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
created_leaves = [] |
||||
|
|
||||
|
with transaction.atomic(): |
||||
|
self.stdout.write('Building Rich Islamic Category Tree...') |
||||
|
|
||||
|
for source_code, sect_data in ISLAMIC_DATA.items(): |
||||
|
for sect_type_enum, categories_list in sect_data.items(): |
||||
|
|
||||
|
current_sect = shia_sect if sect_type_enum == HadisSect.SectType.SHIA else sunni_sect |
||||
|
|
||||
|
for root_title, sub_title, leaf_titles in categories_list: |
||||
|
|
||||
|
# --- Level 1: Root --- |
||||
|
# ADDED: description to defaults |
||||
|
root_cat, _ = HadisCategory.objects.get_or_create( |
||||
|
sect=current_sect, |
||||
|
source_type=source_code, |
||||
|
title=[{'text': root_title, 'language_code': 'en'}], |
||||
|
defaults={ |
||||
|
'parent': None, |
||||
|
'description': [{'text': f'Root category for {root_title}', 'language_code': 'en'}] |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
# --- Level 2: Sub --- |
||||
|
# ADDED: description to defaults |
||||
|
sub_cat, _ = HadisCategory.objects.get_or_create( |
||||
|
sect=current_sect, |
||||
|
source_type=source_code, |
||||
|
title=[{'text': sub_title, 'language_code': 'en'}], |
||||
|
defaults={ |
||||
|
'parent': root_cat, |
||||
|
'description': [{'text': f'Sub-category for {sub_title}', 'language_code': 'en'}] |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
# --- Level 3: Leaves --- |
||||
|
for leaf_title in leaf_titles: |
||||
|
# ADDED: description to defaults |
||||
|
leaf_cat, _ = HadisCategory.objects.get_or_create( |
||||
|
sect=current_sect, |
||||
|
source_type=source_code, |
||||
|
title=[{'text': leaf_title, 'language_code': 'en'}], |
||||
|
defaults={ |
||||
|
'parent': sub_cat, |
||||
|
'description': [{'text': f'Leaf category for {leaf_title}', 'language_code': 'en'}] |
||||
|
} |
||||
|
) |
||||
|
created_leaves.append(leaf_cat) |
||||
|
self.stdout.write(f"Created Leaf: {leaf_title} ({current_sect.get_title('en')})") |
||||
|
|
||||
|
self.stdout.write(self.style.SUCCESS(f'Successfully structured {len(created_leaves)} leaf categories.')) |
||||
|
|
||||
|
# 3. Connect Hadiths to Leaves |
||||
|
all_hadiths = list(Hadis.objects.all()) |
||||
|
total_hadiths = len(all_hadiths) |
||||
|
|
||||
|
if total_hadiths < 3: |
||||
|
self.stdout.write(self.style.ERROR('Not enough Hadiths in DB. Please seed Hadiths first (need at least 3).')) |
||||
|
return |
||||
|
|
||||
|
self.stdout.write(f'Distributing {total_hadiths} Hadiths among leaves...') |
||||
|
|
||||
|
random.shuffle(all_hadiths) |
||||
|
hadith_index = 0 |
||||
|
|
||||
|
for leaf in created_leaves: |
||||
|
if hadith_index + 3 > total_hadiths: |
||||
|
hadith_index = 0 |
||||
|
|
||||
|
batch = all_hadiths[hadith_index : hadith_index + 3] |
||||
|
hadith_index += 3 |
||||
|
|
||||
|
for hadis_obj in batch: |
||||
|
hadis_obj.category = leaf |
||||
|
hadis_obj.save() |
||||
|
|
||||
|
self.stdout.write(self.style.SUCCESS('--- Islamic Category Seeding Complete ---')) |
||||
@ -0,0 +1,103 @@ |
|||||
|
#!/usr/bin/env python |
||||
|
import os |
||||
|
import django |
||||
|
from pathlib import Path |
||||
|
|
||||
|
# Setup Django |
||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.develop') |
||||
|
django.setup() |
||||
|
|
||||
|
from city_detection_ip import CITY_DB_PATH |
||||
|
import geoip2.database |
||||
|
|
||||
|
def test_maxmind_capabilities(): |
||||
|
"""Test what the MaxMind GeoLite2 database can actually do""" |
||||
|
|
||||
|
if not CITY_DB_PATH.exists(): |
||||
|
print(f"❌ Database file not found: {CITY_DB_PATH}") |
||||
|
return |
||||
|
|
||||
|
print(f"✅ Found MaxMind database: {CITY_DB_PATH}") |
||||
|
|
||||
|
try: |
||||
|
with geoip2.database.Reader(CITY_DB_PATH) as reader: |
||||
|
# Show what methods are available on the reader |
||||
|
print("\n📋 Available methods on MaxMind reader:") |
||||
|
methods = [method for method in dir(reader) if not method.startswith('_')] |
||||
|
for method in methods: |
||||
|
print(f" - {method}") |
||||
|
|
||||
|
# Test IP lookup (this works) |
||||
|
print("\n🧪 Testing IP lookup (should work):") |
||||
|
try: |
||||
|
response = reader.city('8.8.8.8') # Google DNS IP |
||||
|
print(f" IP 8.8.8.8 → City: {response.city.name}, Country: {response.country.iso_code}") |
||||
|
except Exception as e: |
||||
|
print(f" ❌ IP lookup failed: {e}") |
||||
|
|
||||
|
# Try to find coordinate-based methods (these won't exist) |
||||
|
print("\n🔍 Checking for coordinate-based methods:") |
||||
|
coord_methods = ['city_by_coords', 'location', 'reverse', 'geocode', 'coordinates', 'latlon'] |
||||
|
found_coord_methods = [m for m in methods if any(coord in m.lower() for coord in coord_methods)] |
||||
|
|
||||
|
if found_coord_methods: |
||||
|
print(f" ⚠️ Found potential coordinate methods: {found_coord_methods}") |
||||
|
for method in found_coord_methods: |
||||
|
try: |
||||
|
method_obj = getattr(reader, method) |
||||
|
print(f" - {method}: {method_obj}") |
||||
|
except Exception as e: |
||||
|
print(f" - {method}: Error - {e}") |
||||
|
else: |
||||
|
print(" ✅ No coordinate-based methods found (as expected)") |
||||
|
|
||||
|
# Demonstrate the limitation |
||||
|
print("\n🚫 Demonstrating the limitation:") |
||||
|
print(" MaxMind databases only support IP → Location lookups") |
||||
|
print(" They do NOT support Coordinates → Location lookups") |
||||
|
print(" For reverse geocoding, you need a spatial database like our geonames_city table") |
||||
|
|
||||
|
except Exception as e: |
||||
|
print(f"❌ Error opening database: {e}") |
||||
|
|
||||
|
def test_coordinate_lookup_attempt(): |
||||
|
"""Try to 'force' a coordinate lookup to show it doesn't work""" |
||||
|
|
||||
|
print("\n🎯 Attempting coordinate lookup (this will fail):") |
||||
|
|
||||
|
if not CITY_DB_PATH.exists(): |
||||
|
return |
||||
|
|
||||
|
try: |
||||
|
with geoip2.database.Reader(CITY_DB_PATH) as reader: |
||||
|
# Try various ways to pass coordinates (all will fail) |
||||
|
test_coords = [ |
||||
|
"5.161.94.84", # Tehran coordinates |
||||
|
"35.698,51.4115", # String format |
||||
|
[35.698, 51.4115], # List format |
||||
|
] |
||||
|
|
||||
|
for coords in test_coords: |
||||
|
try: |
||||
|
# This will raise AttributeError - no such method exists |
||||
|
response = reader.city(coords) |
||||
|
print(f" ❌ Unexpectedly succeeded with {coords}: {response}") |
||||
|
except AttributeError as e: |
||||
|
print(f" ✅ Expected error with {coords}: {e}") |
||||
|
except Exception as e: |
||||
|
print(f" ❓ Unexpected error with {coords}: {e}") |
||||
|
|
||||
|
except Exception as e: |
||||
|
print(f"❌ Database error: {e}") |
||||
|
|
||||
|
if __name__ == "__main__": |
||||
|
print("🧪 Testing MaxMind GeoLite2 Database Capabilities") |
||||
|
print("=" * 50) |
||||
|
|
||||
|
test_maxmind_capabilities() |
||||
|
test_coordinate_lookup_attempt() |
||||
|
|
||||
|
print("\n" + "=" * 50) |
||||
|
print("🎯 CONCLUSION:") |
||||
|
print("MaxMind GeoLite2 databases are for IP geolocation only.") |
||||
|
print("Use the geonames_city table for coordinate-based reverse geocoding.") |
||||
227583
utils/geonames_data/cities500.txt
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue