8 changed files with 228015 additions and 493 deletions
-
175apps/account/management/commands/create_geonames_table.py
-
4apps/account/views/user.py
-
581apps/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.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): |
|||
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): |
|||
if options['clear']: |
|||
self._clear_seeded_data() |
|||
self.stdout.write(self.style.WARNING('--- Starting Islamic Category Seeding ---')) |
|||
|
|||
# Ensure sects exist |
|||
try: |
|||
shia_sect = HadisSect.objects.get(sect_type=HadisSect.SectType.SHIA) |
|||
except HadisSect.DoesNotExist: |
|||
shia_sect = HadisSect.objects.create( |
|||
# 1. Fetch or Create Sects |
|||
shia_sect, _ = HadisSect.objects.get_or_create( |
|||
sect_type=HadisSect.SectType.SHIA, |
|||
title="Shia", |
|||
description="Default Shia sect" |
|||
defaults={'title': [{'text': 'Shia (Twelver)', 'language_code': 'en'}], 'is_active': True} |
|||
) |
|||
|
|||
try: |
|||
sunni_sect = HadisSect.objects.get(sect_type=HadisSect.SectType.SUNNI) |
|||
except HadisSect.DoesNotExist: |
|||
sunni_sect = HadisSect.objects.create( |
|||
sunni_sect, _ = HadisSect.objects.get_or_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, |
|||
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"]) |
|||
] |
|||
).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, |
|||
}, |
|||
) |
|||
|
|||
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, |
|||
}, |
|||
) |
|||
|
|||
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, |
|||
}, |
|||
) |
|||
|
|||
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.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"]) |
|||
] |
|||
}, |
|||
) |
|||
|
|||
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, |
|||
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"]) |
|||
] |
|||
}, |
|||
) |
|||
|
|||
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, |
|||
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"]) |
|||
] |
|||
}, |
|||
) |
|||
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)"]) |
|||
] |
|||
} |
|||
} |
|||
|
|||
# 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"]], |
|||
) |
|||
created_leaves = [] |
|||
|
|||
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"]], |
|||
) |
|||
with transaction.atomic(): |
|||
self.stdout.write('Building Rich Islamic Category Tree...') |
|||
|
|||
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"]], |
|||
) |
|||
for source_code, sect_data in ISLAMIC_DATA.items(): |
|||
for sect_type_enum, categories_list in sect_data.items(): |
|||
|
|||
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"]], |
|||
) |
|||
current_sect = shia_sect if sect_type_enum == HadisSect.SectType.SHIA else sunni_sect |
|||
|
|||
# ---------------- QUOTE ---------------- # |
|||
for root_title, sub_title, leaf_titles in categories_list: |
|||
|
|||
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", |
|||
# --- 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={ |
|||
"description": "Short wise sayings and inspirational quotes.", |
|||
"order": 1, |
|||
}, |
|||
'parent': None, |
|||
'description': [{'text': f'Root category for {root_title}', 'language_code': 'en'}] |
|||
} |
|||
) |
|||
|
|||
short_wisdom, _ = HadisCategory.objects.get_or_create( |
|||
sect=sect, |
|||
source_type=HadisCategory.SourceType.QUOTE, |
|||
parent=root, |
|||
title="Short Wisdom", |
|||
# --- 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={ |
|||
"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", |
|||
'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={ |
|||
"description": "Quotes emphasizing the virtue of knowledge and learning.", |
|||
"order": 2, |
|||
}, |
|||
'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')})") |
|||
|
|||
# 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.stdout.write(self.style.SUCCESS(f'Successfully structured {len(created_leaves)} leaf categories.')) |
|||
|
|||
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 ---------------- # |
|||
# 3. Connect Hadiths to Leaves |
|||
all_hadiths = list(Hadis.objects.all()) |
|||
total_hadiths = len(all_hadiths) |
|||
|
|||
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 total_hadiths < 3: |
|||
self.stdout.write(self.style.ERROR('Not enough Hadiths in DB. Please seed Hadiths first (need at least 3).')) |
|||
return |
|||
|
|||
if tags: |
|||
hadis.tags.set(tags) |
|||
return hadis |
|||
self.stdout.write(f'Distributing {total_hadiths} Hadiths among leaves...') |
|||
|
|||
def _print_summary(self): |
|||
from apps.hadis.models import HadisCategory, Hadis |
|||
random.shuffle(all_hadiths) |
|||
hadith_index = 0 |
|||
|
|||
self.stdout.write("\n" + "=" * 70) |
|||
self.stdout.write(self.style.SUCCESS("CATEGORY & HADIS SUMMARY")) |
|||
self.stdout.write("=" * 70) |
|||
for leaf in created_leaves: |
|||
if hadith_index + 3 > total_hadiths: |
|||
hadith_index = 0 |
|||
|
|||
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() |
|||
batch = all_hadiths[hadith_index : hadith_index + 3] |
|||
hadith_index += 3 |
|||
|
|||
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() |
|||
for hadis_obj in batch: |
|||
hadis_obj.category = leaf |
|||
hadis_obj.save() |
|||
|
|||
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") |
|||
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