Mini Shell
import time
from typing import Generator
import blinker
from peewee import (
JOIN,
CharField,
Check,
DoesNotExist,
ForeignKeyField,
IntegerField,
)
from playhouse.shortcuts import model_to_dict
from defence360agent.model import instance, Model
from defence360agent.model.simplification import apply_order_by
from im360.internals import geo
class Country(Model):
"""Contains a single record per country, with its code and name."""
id = CharField(primary_key=True, null=False)
#: Country code, e.g. "US".
code = CharField(max_length=2, unique=True, null=False)
#: Country name, e.g. "United States".
name = CharField(null=False)
class Meta:
db_table = "country"
database = instance.db
@classmethod
def update_from(cls, countries):
"""
Load country info from iterable of dicts to database
:param countries:
:return:
"""
for country in countries:
cls.insert(country).on_conflict_replace().execute()
class CountryList(Model):
"""List of Countries in user WHITE/BLACK list."""
#: Listname for blocked countries.
BLACK = "BLACK"
#: Listname for whitelisted countries (not officially supported).
WHITE = "WHITE"
#: The link to country code and name in :class:`Country`.
country = ForeignKeyField(Country, primary_key=True, null=False)
#: In which list all IPs from this country should be:
#: :attr:`BLACK` or :attr:`WHITE`.
listname = CharField(
null=False, constraints=[Check("listname in ('WHITE','BLACK')")]
)
#: Timestamp when the record was added.
ctime = IntegerField(null=True, default=lambda: int(time.time()))
#: Comment set by admin.
comment = CharField(null=True)
class Meta:
db_table = "country_list"
database = instance.db
class Signals:
added = blinker.Signal()
deleted = blinker.Signal()
@classmethod
def create(cls, **kwargs):
obj = super().create(**kwargs)
cls.Signals.added.send(obj.listname, country_id=obj.country_id)
return obj
@classmethod
def fetch_as_union(cls, **filter_args):
return cls._fetch_filter(skip_order_by=True, **filter_args)
@classmethod
def _fetch_filter(
cls,
by_country_code=None,
by_comment=None,
by_ip=None,
by_list=None,
skip_order_by=False,
):
country_code_from_ip = None
if by_ip:
with geo.reader() as geo_reader:
# country_code_from_ip will be None if here partial IP is provided # noqa: E501
country_code_from_ip = geo_reader.get_code(by_ip)
q = (
CountryList.select(CountryList)
.distinct()
.join(Country, JOIN.INNER, on=(CountryList.country == Country.id))
)
if by_list:
q = q.where(CountryList.listname.in_(by_list))
if not skip_order_by:
q = q.order_by(Country.code)
# filter by optional args
if by_country_code:
q = q.where(Country.code == by_country_code)
if by_comment:
q = q.where(cls.comment.contains(by_comment))
if by_ip:
q = q.where(Country.code == country_code_from_ip)
return q
@classmethod
def fetch_count(cls, **filter_args):
return cls._fetch_filter(**filter_args).count()
@classmethod
def fetch(cls, offset=None, limit=None, order_by=None, **filter_args):
q = cls._fetch_filter(**filter_args)
if offset is not None:
q = q.offset(offset)
if limit is not None:
q = q.limit(limit)
if order_by is not None:
q = apply_order_by(order_by, cls, q)
return [model_to_dict(row) for row in q]
@classmethod
def get_listname(cls, country):
try:
return cls.get(
country=Country.select().where(Country.code == country)
).listname
except DoesNotExist:
return None
@classmethod
def delete_country(cls, country: Country, listname):
deleted = (
CountryList.delete()
.where(
(CountryList.country == country)
& (CountryList.listname == listname)
)
.execute()
)
if deleted:
cls.Signals.deleted.send(listname, country_id=country.id)
return deleted
@classmethod
def country_codes(cls, listname) -> Generator:
"""Returns generator of listed country codes."""
q = (
CountryList.select(Country.code)
.distinct()
.join(Country, JOIN.INNER, on=(CountryList.country == Country.id))
.where(CountryList.listname == listname)
.order_by(Country.code)
)
return (code for (code,) in q.tuples())