class Paginator(BasePaginator):

    def __init__(self, page=1, expander=None, *args, **kwargs):
        super(Paginator, self).__init__(*args, **kwargs)
        self.expander = expander
        self.current_page = self.page(page)

    def page_range_slice(self):
        left = self.current_page.number - 2
        right = self.current_page.number + 2
        overflow_left = max(0, (-1 * left) + 1)
        overflow_right = max(0, right - self.num_pages)
        left = max(left - overflow_right, 1)
        right = min(right + overflow_left, self.num_pages)
        return range(left, right + 1)

    def page(self, number):
        '''
        TODO: this method is a copy & paste of supperclass method due to
        the poor Django 1.5 implementation. On 1.6, a '_get_page' factory
        method is provided to overcome this.

        '''
        number = self.validate_number(number)
        bottom = (number - 1) * self.per_page
        top = bottom + self.per_page
        if top + self.orphans >= self.count:
            top = self.count
        return Page(self.object_list[bottom:top], number, self) class Setting(Model):
    __metaclass__ = SettingMetaclass

    name = models.CharField(
        max_length=255,
        null=False,
        db_index=True,
        unique=True
    )
    value = JSONField(
        max_length=1024,
        null=False
    )
    created_at = models.DateTimeField(
        auto_now_add=True,
        null=False
    )
    updated_at = models.DateTimeField(
        auto_now=True,
        null=False
    ) class User(AbstractBaseUser, PermissionsMixin):
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=30, blank=True)
    email = models.EmailField(_('email address'), unique=True)
    is_staff = models.BooleanField(_('staff status'), default=False)
    is_active = models.BooleanField(_('active'), default=True)
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    def get_full_name(self):
        if self.first_name or self.last_name:
            return ', '.join([self.last_name, self.first_name])
        else:
            return self.email

    def get_short_name(self):
        return self.first_name if self.first_name else self.email

    human_name = property(get_full_name)

    def editable_user_permission_labels(self):
        permissions = filter(
            lambda permission: permission.codename in PERMISSIONS,
            self.user_permissions.all())
        return [PERMISSIONS[permission.codename] for permission in permissions]

    class Meta:
        permissions = (
            ('can_access_advanced_ban_submission',
             'Access advanced ban submission form'),
            ('can_access_expert_ban_submission',
             'Access expert ban submission form'),
            ('can_access_bans_submissions',
             'Access bans submissions'),
            ('can_access_bans_status',
             'Access bans status'),
            ('can_access_caches_management',
             'Access caches management'),
            ('can_access_users_management',
             'Access users management'),
            ('can_access_settings',
             'Access settings'),
        )
        verbose_name = _('user')
        verbose_name_plural = _('users') class Group(Cache):
    name = models.CharField(
        _('Name'),
        help_text=_(
            'Some name used internally by VBM to refer to the group of '
            'caching nodes.'),
        max_length=255,
        null=False
    )
    weight = models.SmallIntegerField(
        default=0,
        null=False
    )
    revision = RevisionField()
    created_at = models.DateTimeField(
        auto_now_add=True,
        null=False
    )
    updated_at = models.DateTimeField(
        auto_now=True,
        null=False
    )

    # Cache methods.
    def _human_name(self):
        return self.name

    human_name = property(_human_name)

    def _items(self):
        return self.nodes.all().order_by('weight', 'created_at')

    items = property(_items)

    class Meta:
        verbose_name = _('cache group')
        verbose_name_plural = _('cache groups') class Node(Cache):
    VERSION_21 = 21
    VERSION_30 = 30
    VERSION_CHOICES = (
        (VERSION_21, '2.1'),
        (VERSION_30, '>= 3.0'),
    )

    name = models.CharField(
        _('Name'),
        help_text=_(
            'Some name used internally by VBM to refer to the cache node. If '
            'not provided, the host and port number of the node will be '
            'used.'),
        max_length=255,
        null=True,
        blank=True
    )
    host = models.CharField(
        _('Host'),
        help_text=_(
            'Name or IP address of the server running the Varnish cache '
            'node.'),
        max_length=255,
        null=False
    )
    port = models.PositiveIntegerField(
        _('Port'),
        help_text=_('Varnish management port number.'),
        null=False
    )
    secret = models.TextField(
        _('Secret'),
        help_text=_(
            'If the -S secret-file is used in the cache node, provide here '
            'the contents of that file in order to authenticate CLI '
            'connections opened by VBM.'),
        max_length=65536,
        null=True,
        blank=True,
    )
    version = models.SmallIntegerField(
        _('Version'),
        help_text=_('Select the Varnish version running in the cache node.'),
        choices=VERSION_CHOICES,
        default=VERSION_30,
        null=False
    )
    weight = models.SmallIntegerField(
        default=0,
        null=False
    )
    group = models.ForeignKey(
        Group,
        related_name='nodes',
        null=True,
        blank=True,
        on_delete=models.SET_NULL
    )
    revision = RevisionField()
    created_at = models.DateTimeField(
        auto_now_add=True,
        null=False
    )
    updated_at = models.DateTimeField(
        auto_now=True,
        null=False
    )

    def ban(self, expression):
        self._cli('ban', expression)

    def ban_list(self):
        return self._cli('ban_list')

    def _cli(self, name, *args):
        varnish = Varnish(
            self.host, int(self.port), self.human_name,
            secret=self.secret,
            version=self.version)
        try:
            return getattr(varnish, name)(*args)
        finally:
            varnish.quit()

    # Cache methods.

    def _human_name(self):
        return self.name if self.name else '%s:%d' % (self.host, self.port,)

    human_name = property(_human_name)

    def _items(self):
        return [self]

    items = property(_items)

    class Meta:
        verbose_name = _('cache node')
        verbose_name_plural = _('cache nodes') class BanSubmission(Model):
    BASIC_TYPE = 1
    ADVANCED_TYPE = 2
    EXPERT_TYPE = 3

    BAN_TYPE_CHOICES = (
        (BASIC_TYPE, _('basic')),
        (ADVANCED_TYPE, _('advanced')),
        (EXPERT_TYPE, _('expert')),
    )

    user = models.ForeignKey(
        User,
        related_name='ban_submissions',
        null=False
    )
    launched_at = models.DateTimeField(
        null=False
    )
    ban_type = models.PositiveSmallIntegerField(
        null=False,
        choices=BAN_TYPE_CHOICES
    )
    expression = models.CharField(
        max_length=2048,
        null=False
    )
    target_content_type = models.ForeignKey(ContentType)
    target_id = models.PositiveIntegerField()
    target = GenericForeignKey('target_content_type', 'target_id')
    created_at = models.DateTimeField(
        auto_now_add=True,
        null=False
    )
    updated_at = models.DateTimeField(
        auto_now=True,
        null=False
    )

    def _human_ban_type_name(self):
        return dict(self.BAN_TYPE_CHOICES)[self.ban_type]

    human_ban_type_name = property(_human_ban_type_name) class FallbackIntegerField(FallbackMixinField, IntegerField):
    pass class BetterChoiceField(ChoiceField):
    def __init__(self, choices=(), placeholder=None, *args, **kwargs):
        placeholder_with_decorations = \
            string_concat('- ', placeholder, ' -') if placeholder else u''
        choices = [(None, placeholder_with_decorations)] + list(choices)
        super(BetterChoiceField, self).__init__(
            choices=choices, *args, **kwargs)

    def clean(self, value):
        value = super(BetterChoiceField, self).clean(value)
        return None if value == u'' else value class FallbackCharField(FallbackMixinField, CharField):
    pass class SortDirectionField(FallbackCharField):
    def __init__(self, *args, **kwargs):
        assert 'choices' not in kwargs, 'No custom choices can be set for a'\
            'SortDirectionField'
        kwargs['choices'] = ['asc', 'desc']
        super(SortDirectionField, self).__init__(*args, **kwargs)
import re
from urlparse import urlparse
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.utils.encoding import force_text
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from varnish_bans_manager.core.helpers.paginator import Paginator
from varnish_bans_manager.core.models import BanSubmission, Node, Group, Setting, User
from varnish_bans_manager.core.forms.base import FallbackIntegerField, BetterChoiceField, FallbackCharField, SortDirectionField


class SubmitForm(forms.Form):
    target = TargetField(
        label=_('Target'),
        placeholder=_('select'))

    def __init__(self, user, *args, **kwargs):
        super(SubmitForm, self).__init__(*args, **kwargs)
        self.user = user

    def _merge_base_ban_expression(self, expression):
        base_ban_expression = Setting.base_ban_expression
        if base_ban_expression != '':
            return '%s && %s' % (base_ban_expression, expression)
        return expression

    def _ban_submission(self):
        return BanSubmission(
            user=self.user,
            ban_type=self.ban_type,
            expression=self.expression,
            target=self.cleaned_data.get('target'))

    ban_submission = property(_ban_submission)


class BasicForm(SubmitForm):
    ban_type = BanSubmission.BASIC_TYPE

    url = forms.URLField(
        label=_('URL'),
        widget=forms.TextInput(attrs={
            'placeholder': _('URL to be removed from caches'),
        }),
        max_length=2048)

    error_messages = {
        'invalid_url_scheme': _(
            'Only http:// and https:// schemes are supported.')
    }

    def __init__(self, *args, **kwargs):
        super(BasicForm, self).__init__(*args, **kwargs)
        self.fields['target'].load_choices(expert=False)

    def clean_url(self):
        url = self.cleaned_data['url']
        if not re.compile(r'^https?://').match(url):
            raise forms.ValidationError(
                self.error_messages['invalid_url_scheme'])
        return url

    def _expression(self):
        parsed = urlparse(self.cleaned_data['url'])
        return self._merge_base_ban_expression('%s == "%s" && %s == "%s"' % (
            Setting.host_matching_variable,
            parsed.netloc,
            Setting.url_matching_variable,
            parsed.path,
        ))

    expression = property(_expression)


class AdvancedForm(SubmitForm):
    ban_type = BanSubmission.ADVANCED_TYPE

    regular_expression = forms.CharField(
        label=_('Regular expression'),
        widget=forms.TextInput(attrs={
            'placeholder': _('match contents to be removed from caches'),
        }),
        max_length=1024)

    def __init__(self, *args, **kwargs):
        super(AdvancedForm, self).__init__(*args, **kwargs)
        self.fields['target'].load_choices(expert=False)

    def _expression(self):
        regular_expression = self.cleaned_data.get('regular_expression')
        return self._merge_base_ban_expression('%s ~ %s' % (
            Setting.url_matching_variable,
            regular_expression,
        ))

    expression = property(_expression)


class ExpertForm(SubmitForm):
    ban_type = BanSubmission.EXPERT_TYPE

    ban_expression = forms.CharField(
        label=_('Ban expression'),
        widget=forms.Textarea(attrs={
            'placeholder': _('match contents to be removed from caches'),
            'rows': 4,
        }),
        max_length=1024)

    def __init__(self, *args, **kwargs):
        super(ExpertForm, self).__init__(*args, **kwargs)
        self.fields['target'].load_choices(expert=True)

    def _expression(self):
        return self.cleaned_data.get('ban_expression')

    expression = property(_expression)


class SubmissionsForm(forms.Form):
    ITEMS_PER_PAGE_CHOICES = [10, 20, 50]
    SORT_CRITERIA_CHOICES = (
        ('launched_at', _('Submission date')),
    )

    user = BetterChoiceField(
        choices=(),
        required=False,
        placeholder=_('all users'))

    ban_type = BetterChoiceField(