geoobjects / geoobjects / models.py

# -*- encoding: utf-8 -*-
import struct
import socket
from django.db import models
from django.core.exceptions import ObjectDoesNotExist
from django.core.cache import cache
from django.core.exceptions import FieldError
from django.utils.functional import cached_property


__author__ = 'vadim'

DELIM = ':'

DEFAULT_GEO_LOCATION = 5279


class GeobazaRanges(models.Model):
    start = models.CharField(max_length=15)
    end = models.CharField(max_length=15)
    ip_int = models.BigIntegerField()
    length = models.BigIntegerField()
    tld = models.CharField(max_length=4, null=True)
    obj_id = models.PositiveIntegerField(max_length=11)

    class Meta:
        unique_together = ('ip_int', 'length')

    def __unicode__(self):
        return u"%s-%s" % (self.start, self.end)

    @classmethod
    def get_obj(cls, ip):
        int_ip = struct.unpack('>L', socket.inet_aton(ip))[0]
        key_cache = "geo_range_" + str(ip)
        ob = cache.get(key_cache)
        if ob is None:
            try:
                ob = cls.objects.all().extra(where=['ip_int <= %d AND ip_int + length >=%d' % (int_ip, int_ip)],
                                             order_by=['length']).get()
                cache.set(key_cache, ob)
            except ObjectDoesNotExist:
                ob = None
        return ob

    @cached_property
    def obj(self):
        key_cache = "geo_obj_" + str(self.obj_id)
        ob = cache.get(key_cache)
        if ob is None:
            try:
                ob = GeobazaObjects.objects.get(pk=self.obj_id)
            except ObjectDoesNotExist:
                ob = GeobazaObjects.objects.get(pk=DEFAULT_GEO_LOCATION)
            cache.set(key_cache, ob)
        return ob


class GeobazaObjects(models.Model):
    parent_id = models.PositiveIntegerField()
    type = models.CharField(max_length=100)
    name_en = models.CharField(max_length=255)
    name_ru = models.CharField(max_length=255, null=True, default=None)
    lat = models.FloatField(null=True, default=None)
    lon = models.FloatField(null=True, default=None)

    def __unicode__(self):
        return u"%s:%s" % (self.type, self.name_en)

    @classmethod
    def get_obj(cls, obj_id):
        key_cache = "geo_obj_%d" % obj_id
        ob = cache.get(key_cache)
        if ob is None:
            ob = GeobazaObjects.objects.get(pk=obj_id)
            cache.set(key_cache, ob)
        return ob

    @cached_property
    def parent(self):
        try:
            return GeobazaObjects.objects.get(pk=self.parent_id)
        except ObjectDoesNotExist:
            return None

    @cached_property
    def child(self):
        return GeobazaObjects.objects.all().filter(parent_id=self.id)

    def get_path(self, reverse=False):
        path = [self]
        parent = self.parent
        while parent:
            path.append(parent)
            parent = parent.parent
        if reverse:
            path.reverse()
        return path

    def to_dict(self, lang='ru'):
        return {self.id: self.name(lang)}

    @cached_property
    def path(self):
        return self.get_path()

    @cached_property
    def country(self):
        for obj in self.path:
            if obj.type == 'country':
                return obj
        return None

    def name(self, lang='en'):
        return u', '.join([path.name_ru if lang == 'ru' and path.name_ru else path.name_en
                           for path in self.get_path(False)])

    @cached_property
    def dump_path(self):
        path = self.path
        if not path:  # or (hasattr(path, 'is_special') and path.is_special):
            return ''
        ret = DELIM.join([str(obj.id) for obj in path])
        return DELIM + ret + DELIM

    @cached_property
    def dump_key(self):
        return DELIM + str(self.id) + DELIM


class GeoField(models.CharField):
    __metaclass__ = models.SubfieldBase
    description = "Stores GEO data"

    def __init__(self, *args, **kwargs):
        self.type_field = kwargs.get('type', list)  # list (default) or dict
        super(GeoField, self).__init__(*args, **kwargs)

    def get_prep_value(self, value):
        if value is None:
            return None
        if isinstance(value, (list, tuple)) and len(value) > 1:
            ob = GeobazaObjects.get_obj(value[0])
            value = ob.dump_path if ob else None
        elif isinstance(value, dict) and len(value):
            ob = GeobazaObjects.get_obj(value.keys()[0])
            value = ob.dump_path if ob else None
        elif isinstance(value, int):
            ob = GeobazaObjects.get_obj(value)
            value = ob.dump_path if ob else None
        elif isinstance(value, basestring) and DELIM in value:
            pass
        else:
            raise FieldError('GeoField take value type=%s. Only dict, int, path support!' % type(value))

        return value

    def to_python(self, value):
        if isinstance(value, basestring) and len(value):
            try:
                val = int(value.split(DELIM)[1]) if DELIM in value else int(value)
                ob = GeobazaObjects.get_obj(val)
                if ob:
                    value = ob.to_dict()
                    if self.type_field == list:
                        value = value.items()[0]
            except:
                pass
        return value

    def south_field_triple(self):
        """Returns a suitable description of this field for South."""
        # We'll just introspect the _actual_ field.
        try:
            from south.modelsinspector import introspector
            field_class = self.__class__.__module__ + "." + self.__class__.__name__
            args, kwargs = introspector(self)
            # That's our definition!
            return field_class, args, kwargs
        except:
            pass
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.