Skip to main content

A Django package for Nepali address selection with dynamic form fields

Project description

Nepali Address Picker

A Django package that provides a dynamic address picker for Nepali addresses, including provinces, districts, and local bodies (municipalities, sub-metropolitan cities, and rural municipalities).

Features

  • Dynamic form with cascading dropdowns
  • AJAX-based updates for better user experience
  • Bootstrap styling for a clean interface
  • Support for all Nepali administrative divisions
  • Easy integration with existing Django projects

Installation

  1. Clone the repository:
git clone https://github.com/yourusername/nepali_address_picker.git
  1. Install the package in your Django project:
pip install -e .

Usage

  1. Add address_picker to your INSTALLED_APPS in settings.py:
INSTALLED_APPS = [
    ...
    'address_picker',
    ...
]
  1. Include the URLs in your project's urls.py:
from django.urls import path, include

urlpatterns = [
    ...
    path('address/', include('address_picker.urls')),
    ...
]
  1. Load the address data using the management command:
python manage.py load_nepali_addresses
  1. Use the form in your views:
from address_picker.forms import NepaliAddressForm

def your_view(request):
    form = NepaliAddressForm(request.POST or None)
    if request.method == 'POST' and form.is_valid():
        province = form.cleaned_data['province']
        district = form.cleaned_data['district']
        local_body = form.cleaned_data['local_body']
        # Process the data as needed
    return render(request, 'your_template.html', {'form': form})
  1. Include the form in your template:
{% extends 'base.html' %}

{% block content %}
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Submit</button>
</form>

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(function() {
    $('#id_province').change(function() {
        var url = "{% url 'ajax_load_districts' %}";
        var provinceId = $(this).val();
        $.ajax({
            url: url,
            data: {
                'province': provinceId
            },
            success: function(data) {
                var $district = $('#id_district');
                $district.html('<option value="">Select District</option>');
                $.each(data, function(i, item) {
                    $district.append('<option value="' + item.id + '">' + item.name + '</option>');
                });
                $('#id_local_body').html('<option value="">Select Local Body</option>');
            }
        });
    });

    $('#id_district').change(function() {
        var url = "{% url 'ajax_load_local_bodies' %}";
        var districtId = $(this).val();
        $.ajax({
            url: url,
            data: {
                'district': districtId
            },
            success: function(data) {
                var $localBody = $('#id_local_body');
                $localBody.html('<option value="">Select Local Body</option>');
                $.each(data, function(i, item) {
                    $localBody.append('<option value="' + item.id + '">' + item.name + ' (' + item.type + ')</option>');
                });
            }
        });
    });
});
</script>
{% endblock %}

Models

The package includes the following models:

  • Province: Represents Nepali provinces
  • District: Represents districts within provinces
  • Municipality: Represents municipalities within districts
  • SubMetropolitan: Represents sub-metropolitan cities within districts
  • RuralMunicipality: Represents rural municipalities within districts

Forms

The package provides a NepaliAddressForm that includes:

  • Province selection
  • District selection (dynamically updated based on province)
  • Local body selection (dynamically updated based on district)

Views

The package includes the following views:

  • address_picker: Main view for the address picker form
  • load_districts: AJAX view for loading districts based on province
  • load_local_bodies: AJAX view for loading local bodies based on district

URLs

The package includes the following URLs:

  • /: Main address picker form
  • /ajax/load-districts/: AJAX endpoint for loading districts
  • /ajax/load-local-bodies/: AJAX endpoint for loading local bodies

Dependencies

  • Django 4.2+
  • jQuery 3.6.0+
  • Bootstrap 5.3.0+

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Custom UI Implementation

You can customize the UI while maintaining the functionality. Here's how to implement your own UI:

  1. Create your own template without using the default form rendering:
{% extends 'base.html' %}

{% block content %}
<form method="post" id="address-form">
    {% csrf_token %}
    <div class="your-custom-class">
        <label for="id_province">Province</label>
        <select name="province" id="id_province" class="your-select-class">
            <option value="">Select Province</option>
            {% for province in provinces %}
                <option value="{{ province.id }}">{{ province.name }}</option>
            {% endfor %}
        </select>
    </div>

    <div class="your-custom-class">
        <label for="id_district">District</label>
        <select name="district" id="id_district" class="your-select-class">
            <option value="">Select District</option>
        </select>
    </div>

    <div class="your-custom-class">
        <label for="id_local_body">Local Body</label>
        <select name="local_body" id="id_local_body" class="your-select-class">
            <option value="">Select Local Body</option>
        </select>
    </div>

    <button type="submit" class="your-button-class">Submit</button>
</form>

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
// Your custom JavaScript for handling the dynamic updates
$(function() {
    $('#id_province').change(function() {
        // Your custom AJAX call
    });

    $('#id_district').change(function() {
        // Your custom AJAX call
    });
});
</script>
{% endblock %}
  1. Update your view to pass the initial data:
from address_picker.models import Province

def your_custom_view(request):
    provinces = Province.objects.all()
    return render(request, 'your_custom_template.html', {
        'provinces': provinces
    })

Data Handling

Getting Selected Values

  1. In your view, you can access the selected values:
def your_view(request):
    if request.method == 'POST':
        province_id = request.POST.get('province')
        district_id = request.POST.get('district')
        local_body = request.POST.get('local_body')
        
        # Parse local body type and ID
        local_body_type, local_body_id = local_body.split('-')
        
        # Get the actual objects
        province = Province.objects.get(id=province_id)
        district = District.objects.get(id=district_id)
        
        if local_body_type == 'municipality':
            local_body_obj = Municipality.objects.get(id=local_body_id)
        elif local_body_type == 'submetro':
            local_body_obj = SubMetropolitan.objects.get(id=local_body_id)
        else:
            local_body_obj = RuralMunicipality.objects.get(id=local_body_id)

Using in Models

  1. Add address fields to your model:
from django.db import models
from address_picker.models import Province, District, Municipality, SubMetropolitan, RuralMunicipality

class YourModel(models.Model):
    province = models.ForeignKey(Province, on_delete=models.PROTECT)
    district = models.ForeignKey(District, on_delete=models.PROTECT)
    municipality = models.ForeignKey(Municipality, on_delete=models.PROTECT, null=True, blank=True)
    sub_metropolitan = models.ForeignKey(SubMetropolitan, on_delete=models.PROTECT, null=True, blank=True)
    rural_municipality = models.ForeignKey(RuralMunicipality, on_delete=models.PROTECT, null=True, blank=True)
  1. Create a model form:
from django import forms
from .models import YourModel

class YourModelForm(forms.ModelForm):
    class Meta:
        model = YourModel
        fields = ['province', 'district', 'municipality', 'sub_metropolitan', 'rural_municipality']

API Integration

You can also use the address picker with your API endpoints:

  1. Create API views:
from rest_framework import viewsets
from address_picker.models import Province, District, Municipality, SubMetropolitan, RuralMunicipality
from rest_framework.response import Response

class ProvinceViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Province.objects.all()
    serializer_class = ProvinceSerializer

class DistrictViewSet(viewsets.ReadOnlyModelViewSet):
    def get_queryset(self):
        province_id = self.request.query_params.get('province')
        return District.objects.filter(province_id=province_id)

class LocalBodyViewSet(viewsets.ReadOnlyModelViewSet):
    def get_queryset(self):
        district_id = self.request.query_params.get('district')
        municipalities = Municipality.objects.filter(district_id=district_id)
        submetros = SubMetropolitan.objects.filter(district_id=district_id)
        rurals = RuralMunicipality.objects.filter(district_id=district_id)
        return list(municipalities) + list(submetros) + list(rurals)
  1. Use with frontend frameworks:
// Example using fetch API
async function loadDistricts(provinceId) {
    const response = await fetch(`/api/districts/?province=${provinceId}`);
    const districts = await response.json();
    // Update your UI with the districts
}

async function loadLocalBodies(districtId) {
    const response = await fetch(`/api/local-bodies/?district=${districtId}`);
    const localBodies = await response.json();
    // Update your UI with the local bodies
}

Advanced Usage Examples

1. Custom Form with Validation

from django import forms
from address_picker.forms import NepaliAddressForm

class CustomAddressForm(NepaliAddressForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Add custom validation or field modifications
        self.fields['province'].widget.attrs.update({
            'class': 'custom-select',
            'data-placeholder': 'Choose a province'
        })
        
    def clean(self):
        cleaned_data = super().clean()
        province = cleaned_data.get('province')
        district = cleaned_data.get('district')
        
        # Add custom validation logic
        if province and district and district.province != province:
            raise forms.ValidationError("Selected district does not belong to the selected province")
            
        return cleaned_data

2. Using with Django Formsets

from django.forms import formset_factory
from address_picker.forms import NepaliAddressForm

AddressFormSet = formset_factory(NepaliAddressForm, extra=1)

def multiple_addresses_view(request):
    if request.method == 'POST':
        formset = AddressFormSet(request.POST)
        if formset.is_valid():
            for form in formset:
                # Process each address form
                province = form.cleaned_data['province']
                district = form.cleaned_data['district']
                local_body = form.cleaned_data['local_body']
    else:
        formset = AddressFormSet()
    return render(request, 'multiple_addresses.html', {'formset': formset})

3. Integration with Django Admin

from django.contrib import admin
from address_picker.models import Province, District, Municipality
from address_picker.forms import NepaliAddressForm

class AddressAdmin(admin.ModelAdmin):
    form = NepaliAddressForm
    list_display = ('province', 'district', 'get_local_body')
    
    def get_local_body(self, obj):
        if hasattr(obj, 'municipality'):
            return obj.municipality.name
        elif hasattr(obj, 'sub_metropolitan'):
            return obj.sub_metropolitan.name
        elif hasattr(obj, 'rural_municipality'):
            return obj.rural_municipality.name
        return '-'
    get_local_body.short_description = 'Local Body'

4. Custom JavaScript with Error Handling

// Example of enhanced JavaScript with error handling
$(function() {
    function showError(message) {
        // Your custom error display logic
        alert(message);
    }

    function updateDistricts(provinceId) {
        $.ajax({
            url: "{% url 'ajax_load_districts' %}",
            data: { 'province': provinceId },
            success: function(data) {
                var $district = $('#id_district');
                $district.html('<option value="">Select District</option>');
                $.each(data, function(i, item) {
                    $district.append(
                        $('<option></option>').val(item.id).html(item.name)
                    );
                });
            },
            error: function(xhr, status, error) {
                showError('Failed to load districts: ' + error);
            }
        });
    }

    $('#id_province').change(function() {
        var provinceId = $(this).val();
        if (provinceId) {
            updateDistricts(provinceId);
        } else {
            $('#id_district').html('<option value="">Select District</option>');
            $('#id_local_body').html('<option value="">Select Local Body</option>');
        }
    });
});

5. Using with Django REST Framework Serializers

from rest_framework import serializers
from address_picker.models import Province, District, Municipality, SubMetropolitan, RuralMunicipality

class ProvinceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Province
        fields = ['id', 'name', 'code']

class DistrictSerializer(serializers.ModelSerializer):
    province = ProvinceSerializer(read_only=True)
    
    class Meta:
        model = District
        fields = ['id', 'name', 'province']

class LocalBodySerializer(serializers.ModelSerializer):
    class Meta:
        model = Municipality
        fields = ['id', 'name', 'type']
        
    def to_representation(self, instance):
        data = super().to_representation(instance)
        if isinstance(instance, Municipality):
            data['type'] = 'Municipality'
        elif isinstance(instance, SubMetropolitan):
            data['type'] = 'SubMetropolitan'
        elif isinstance(instance, RuralMunicipality):
            data['type'] = 'RuralMunicipality'
        return data

6. Handling Form Submission with AJAX

// Example of AJAX form submission
$('#address-form').on('submit', function(e) {
    e.preventDefault();
    
    $.ajax({
        url: $(this).attr('action'),
        method: 'POST',
        data: $(this).serialize(),
        success: function(response) {
            // Handle successful submission
            if (response.success) {
                // Update UI or redirect
                window.location.href = response.redirect_url;
            } else {
                // Display validation errors
                displayErrors(response.errors);
            }
        },
        error: function(xhr, status, error) {
            // Handle error
            showError('Failed to submit form: ' + error);
        }
    });
});

function displayErrors(errors) {
    // Clear previous errors
    $('.error-message').remove();
    
    // Display new errors
    Object.keys(errors).forEach(function(field) {
        var errorMessage = errors[field].join(', ');
        $('#' + field).after(
            $('<div></div>')
                .addClass('error-message')
                .text(errorMessage)
        );
    });
}

Admin Panel Configuration

To display and manage the address data in Django's admin panel, add the following to your admin.py:

from django.contrib import admin
from address_picker.models import Province, District, Municipality, SubMetropolitan, RuralMunicipality

@admin.register(Province)
class ProvinceAdmin(admin.ModelAdmin):
    list_display = ('name', 'code')
    search_fields = ('name', 'code')
    ordering = ('name',)

@admin.register(District)
class DistrictAdmin(admin.ModelAdmin):
    list_display = ('name', 'province', 'code')
    list_filter = ('province',)
    search_fields = ('name', 'code')
    ordering = ('province', 'name')
    raw_id_fields = ('province',)

@admin.register(Municipality)
class MunicipalityAdmin(admin.ModelAdmin):
    list_display = ('name', 'district', 'province')
    list_filter = ('district__province', 'district')
    search_fields = ('name',)
    ordering = ('district__province', 'district', 'name')
    raw_id_fields = ('district',)

    def province(self, obj):
        return obj.district.province
    province.short_description = 'Province'

@admin.register(SubMetropolitan)
class SubMetropolitanAdmin(admin.ModelAdmin):
    list_display = ('name', 'district', 'province')
    list_filter = ('district__province', 'district')
    search_fields = ('name',)
    ordering = ('district__province', 'district', 'name')
    raw_id_fields = ('district',)

    def province(self, obj):
        return obj.district.province
    province.short_description = 'Province'

@admin.register(RuralMunicipality)
class RuralMunicipalityAdmin(admin.ModelAdmin):
    list_display = ('name', 'district', 'province')
    list_filter = ('district__province', 'district')
    search_fields = ('name',)
    ordering = ('district__province', 'district', 'name')
    raw_id_fields = ('district',)

    def province(self, obj):
        return obj.district.province
    province.short_description = 'Province'

This configuration provides:

  1. List Views:

    • Province: Shows name and code
    • District: Shows name, province, and code
    • Local Bodies: Shows name, district, and province
  2. Filtering:

    • Filter districts by province
    • Filter local bodies by province and district
  3. Search:

    • Search provinces by name and code
    • Search districts by name and code
    • Search local bodies by name
  4. Ordering:

    • Hierarchical ordering (province → district → name)
    • Alphabetical ordering within each level
  5. Raw ID Fields:

    • Efficient selection of related objects
    • Prevents loading all options in dropdowns

To access the admin panel:

  1. Create a superuser if you haven't already:
python manage.py createsuperuser
  1. Run the development server:
python manage.py runserver
  1. Visit http://127.0.0.1:8000/admin/ and log in with your superuser credentials

You'll see the following sections in the admin panel:

  • Provinces
  • Districts
  • Municipalities
  • Sub Metropolitan Cities
  • Rural Municipalities

Each section provides:

  • List view with sortable columns
  • Search functionality
  • Filtering options
  • Add/Edit/Delete capabilities
  • Related object selection

License

This project is licensed under the MIT License - see the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_nepali_address_picker-0.1.0.tar.gz (45.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_nepali_address_picker-0.1.0-py3-none-any.whl (15.8 kB view details)

Uploaded Python 3

File details

Details for the file django_nepali_address_picker-0.1.0.tar.gz.

File metadata

File hashes

Hashes for django_nepali_address_picker-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ec79cee842b65e1aeb2de44cc5ada5697e309974bff81718f116409012f45e6c
MD5 64fb6e53fda9a78563be7bccab386fbe
BLAKE2b-256 02f72135117e3dda180925d87b5472b04051b1b0d5a8609d8958286ac6bd09b4

See more details on using hashes here.

File details

Details for the file django_nepali_address_picker-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_nepali_address_picker-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 44c00647b7db5989d4bf255c7924cae4b139126316bfcd82ba9e1cf71f153cbf
MD5 d550c7fc2c140ba4f190fb069b87c865
BLAKE2b-256 6b1680c3a32c498f722d75366061162e751914ae03704e7c6455df983be54888

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page