Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kompleta infrastrukturo por pluraj regularoj #330

Merged
merged 7 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions core/admin.py

This file was deleted.

2 changes: 2 additions & 0 deletions core/admin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# flake8:noqa
from .admin import *
189 changes: 189 additions & 0 deletions core/admin/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
from typing import cast

from django.contrib import admin
from django.contrib.flatpages.admin import FlatPageAdmin
from django.contrib.flatpages.models import FlatPage
from django.core.cache import cache
from django.db import models
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _

from djangocodemirror.widgets import CodeMirrorAdminWidget
from packvers import version
from solo.admin import SingletonModelAdmin

from hosting.models import Profile

from ..models import Agreement, Policy, SiteConfiguration, UserBrowser
from .filters import DependentFieldFilter, YearBracketFilter

admin.site.index_template = 'admin/custom_index.html'
admin.site.disable_action('delete_selected')

admin.site.register(SiteConfiguration, SingletonModelAdmin)
admin.site.unregister(FlatPage)


@admin.register(FlatPage)
class FlatPageAdmin(FlatPageAdmin):
formfield_overrides = {
models.TextField: {'widget': CodeMirrorAdminWidget(config_name='html')},
}


@admin.register(Policy)
class PolicyAdmin(admin.ModelAdmin):
list_display = (
'version', 'effective_date', 'requires_consent',
)
ordering = ('-effective_date', )
formfield_overrides = {
models.TextField: {'widget': CodeMirrorAdminWidget(config_name='html')},
}

def has_delete_permission(self, request, obj=None):
return False

def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)

Check warning on line 49 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L49

Added line #L49 was not covered by tests
if 'version' in form.changed_data or 'effective_date' in form.changed_data:
# Bust the cache of references to all policies;
# these references are cached indefinitely otherwise.
cache.delete('all-policies')

Check warning on line 53 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L53

Added line #L53 was not covered by tests


@admin.register(Agreement)
class AgreementAdmin(admin.ModelAdmin):
list_display = (
'id', 'policy_link', 'user_link', 'created', 'modified', 'withdrawn',
)
ordering = ('-policy_version', '-modified', 'user__username')
search_fields = ('user__username',)
list_filter = ('policy_version',)
date_hierarchy = 'created'
fields = (
'user_link', 'policy_link',
'created', 'modified', 'withdrawn',
)
readonly_fields = [f.name for f in Agreement._meta.fields] + ['user_link', 'policy_link']

@admin.display(
description=_("user"),
ordering='user__username',
)
def user_link(self, obj: Agreement):
try:
link = reverse('admin:auth_user_change', args=[obj.user.pk])
account_link = f'<a href="{link}">{obj.user}</a>'
try:
profile_link = '&nbsp;(<a href="{url}">{name}</a>)</sup>'.format(

Check warning on line 80 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L76-L80

Added lines #L76 - L80 were not covered by tests
url=obj.user.profile.get_admin_url(), name=_("profile"))
except Profile.DoesNotExist:
profile_link = ''
return format_html(" ".join([account_link, profile_link]))
except AttributeError:
return format_html('{userid} <sup>?</sup>', userid=obj.user_id)

Check warning on line 86 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L82-L86

Added lines #L82 - L86 were not covered by tests

@admin.display(
description=_("version of policy"),
ordering='policy_version',
)
def policy_link(self, obj: Agreement):
try:
cache = self._policies_cache
except AttributeError:
cache = self._policies_cache = {}

Check warning on line 96 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L93-L96

Added lines #L93 - L96 were not covered by tests
if obj.policy_version in cache:
return cache[obj.policy_version]
value = obj.policy_version
try:
policy = Policy.objects.get(version=obj.policy_version)
link = reverse('admin:core_policy_change', args=[policy.pk])
value = format_html(

Check warning on line 103 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L98-L103

Added lines #L98 - L103 were not covered by tests
'<a href="{url}">{policy}</a>',
url=link, policy=obj.policy_version)
except Policy.DoesNotExist:
pass

Check warning on line 107 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L106-L107

Added lines #L106 - L107 were not covered by tests
finally:
cache[obj.policy_version] = value
return value

Check warning on line 110 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L109-L110

Added lines #L109 - L110 were not covered by tests

def get_queryset(self, request):
qs = super().get_queryset(request).select_related('user', 'user__profile')

Check warning on line 113 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L113

Added line #L113 was not covered by tests
qs = qs.only(
*[f.name for f in Agreement._meta.fields],
'user__id', 'user__username', 'user__profile__id')
return qs

Check warning on line 117 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L117

Added line #L117 was not covered by tests

def has_add_permission(self, request):
return False

def has_change_permission(self, request, obj=None):
return False


@admin.register(UserBrowser)
class UserBrowserAdmin(admin.ModelAdmin):
list_display = (
'user', 'os_name', 'os_version', 'browser_name', 'browser_version', 'added_on',
)
ordering = ('user__username', '-added_on')
search_fields = ('user__username__exact',)
list_filter = (
'os_name',
DependentFieldFilter.configure(
'os_name', 'os_version',
coerce=lambda version_string: version.parse(version_string),
sort=True, sort_reverse=True,
),
'browser_name',
DependentFieldFilter.configure(
'browser_name', 'browser_version',
coerce=lambda version_string: version.parse(version_string),
sort=True, sort_reverse=True,
),
('added_on', YearBracketFilter.configure(YearBracketFilter.Brackets.SINCE)),
)
show_full_result_count = False
fields = (
'user_agent_string', 'user_agent_hash',
'os_name', 'os_version', 'browser_name', 'browser_version', 'device_type',
'geolocation',
)
raw_id_fields = ('user',)
readonly_fields = ('added_on',)

@admin.display(
description=_("user"),
ordering='user__username',
)
def user_link(self, obj: UserBrowser):
try:
link = reverse('admin:auth_user_change', args=[obj.user.pk])
return format_html('<a href="{link}">{user}</a>', link=link, user=obj.user)
except AttributeError:
return format_html('{userid} <sup>?</sup>', userid=obj.user_id)

Check warning on line 166 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L162-L166

Added lines #L162 - L166 were not covered by tests

def get_queryset(self, request):
qs = super().get_queryset(request).select_related('user')

Check warning on line 169 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L169

Added line #L169 was not covered by tests
qs = qs.only(
*[f.name for f in UserBrowser._meta.fields],
'user__id', 'user__username')
return qs

Check warning on line 173 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L173

Added line #L173 was not covered by tests

def get_fields(self, request, obj=None):
return (

Check warning on line 176 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L176

Added line #L176 was not covered by tests
('user' if obj is None else 'user_link',)
+ cast(tuple, self.fields)
+ (('added_on',) if obj is not None else ())
)

def has_change_permission(self, request, obj=None):
return False

def get_form(self, request, *args, **kwargs):
form = super().get_form(request, *args, **kwargs)

Check warning on line 186 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L186

Added line #L186 was not covered by tests
if form.base_fields:
form.base_fields['user_agent_string'].widget.attrs['style'] = "width: 50em;"
return form

Check warning on line 189 in core/admin/admin.py

View check run for this annotation

Codecov / codecov/patch

core/admin/admin.py#L188-L189

Added lines #L188 - L189 were not covered by tests
154 changes: 154 additions & 0 deletions core/admin/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
from enum import Enum
from typing import Callable, Optional, TypedDict, cast

from django.contrib import admin
from django.db.models import Model, QuerySet
from django.http import HttpRequest
from django.utils.functional import lazy
from django.utils.translation import gettext_lazy as _


class YearBracketFilter(admin.DateFieldListFilter):
class Brackets(str, Enum):
EXACT = 'exact'
SINCE = 'since'
UNTIL = 'until'

@classmethod
def configure(cls, bracket=Brackets.EXACT):
if bracket not in (cls.Brackets.SINCE, cls.Brackets.UNTIL):
bracket = cls.Brackets.EXACT

Check warning on line 20 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L20

Added line #L20 was not covered by tests
return type(
f'YearBracket{bracket.capitalize()}Filter',
(cls,),
{
'bracket': bracket,
}
)

def __init__(self, field, request, params, model, model_admin, field_path):
original_field_path = field_path
field_path = f'{field_path}__year'
super().__init__(field, request, params, model, model_admin, field_path)
qs = (

Check warning on line 33 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L30-L33

Added lines #L30 - L33 were not covered by tests
model_admin
.get_queryset(request)
.select_related(None)
.order_by(f'-{original_field_path}')
.only(field_path)
)
qs.query.annotations.clear()
all_years = list(dict.fromkeys(qs.values_list(field_path, flat=True)))

Check warning on line 41 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L40-L41

Added lines #L40 - L41 were not covered by tests
if not hasattr(self, 'bracket'):
self.bracket = self.Brackets.EXACT
self.links = (

Check warning on line 44 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L43-L44

Added lines #L43 - L44 were not covered by tests
(_('Any year'), {}),
)
lookup_kwarg = ''

Check warning on line 47 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L47

Added line #L47 was not covered by tests
if self.bracket is self.Brackets.SINCE:
label_string = _("from %(date)s")
self.lookup_kwarg_since = self.field_generic + 'gte'
lookup_kwarg = self.lookup_kwarg_since

Check warning on line 51 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L49-L51

Added lines #L49 - L51 were not covered by tests
if self.bracket is self.Brackets.UNTIL:
label_string = _("until %(date)s")
self.lookup_kwarg_until = self.field_generic + 'lte'
lookup_kwarg = self.lookup_kwarg_until

Check warning on line 55 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L53-L55

Added lines #L53 - L55 were not covered by tests
if self.bracket is self.Brackets.EXACT:
self.links += tuple(
(str(year), {
self.lookup_kwarg_since: str(year),
self.lookup_kwarg_until: str(year + 1),
})
for year in all_years if year is not None
)
else:
def label(year):

Check warning on line 65 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L65

Added line #L65 was not covered by tests
return lazy(
lambda date=year: (label_string % {'date': date}).capitalize(),
str)
self.links += tuple(
(label(year), {lookup_kwarg: str(year)})
for year in all_years if year is not None
)
if field.null:
self.links += (

Check warning on line 74 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L74

Added line #L74 was not covered by tests
(_('No date'), {self.lookup_kwarg_isnull: str(True)}),
(_('Has date'), {self.lookup_kwarg_isnull: str(False)}),
)


class DependentFieldFilter(admin.SimpleListFilter):
origin_field: str
related_field: str
parameter_name: str
coerce_value: Callable
SortConfig = TypedDict('SortConfig', {'enabled': bool, 'reverse': bool})
sorting: SortConfig

@classmethod
def configure(
cls,
field: str,
related_field: str,
coerce: Optional[Callable] = None,
sort: bool = False,
sort_reverse: Optional[bool] = None,
):
if coerce is None or not callable(coerce):
coerce = lambda v: v
return type(
''.join(related_field.split('_')).capitalize()
+ 'Per' + ''.join(field.split('_')).capitalize()
+ 'Filter',
(cls,),
{
'origin_field': field,
'related_field': related_field,
'parameter_name': related_field,
'coerce_value': lambda filter, value: coerce(value),
'sorting': {
'enabled': bool(sort),
'reverse': bool(sort_reverse),
},
}
)

def lookups(self, request: HttpRequest, model_admin: admin.ModelAdmin):
qs = (

Check warning on line 117 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L117

Added line #L117 was not covered by tests
cast(type[Model], model_admin.model)._default_manager.all()
.filter(**{self.origin_field: self.dependent_on_value})
)
all_values = map(

Check warning on line 121 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L121

Added line #L121 was not covered by tests
self.coerce_value,
qs.values_list(self.related_field, flat=True).distinct())
if self.sorting['enabled']:
all_values = sorted(all_values, reverse=self.sorting['reverse'])

Check warning on line 125 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L125

Added line #L125 was not covered by tests
for val in all_values:
yield (val, str(val))

Check warning on line 127 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L127

Added line #L127 was not covered by tests

def has_output(self):
return bool(self.dependent_on_value)

Check warning on line 130 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L130

Added line #L130 was not covered by tests

def value(self):
val = self.used_parameters.get(self.parameter_name)

Check warning on line 133 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L133

Added line #L133 was not covered by tests
if val in (ch[0] for ch in self.lookup_choices):
return val

Check warning on line 135 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L135

Added line #L135 was not covered by tests
else:
return None

Check warning on line 137 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L137

Added line #L137 was not covered by tests

def queryset(self, request: HttpRequest, queryset: QuerySet):
if self.value():
return queryset.filter(**{self.parameter_name: self.value()})

Check warning on line 141 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L141

Added line #L141 was not covered by tests
else:
return queryset

Check warning on line 143 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L143

Added line #L143 was not covered by tests

def __init__(
self,
request: HttpRequest,
params: dict[str, str],
model: type[Model],
model_admin: admin.ModelAdmin,
):
self.title = model._meta.get_field(self.related_field).verbose_name
self.dependent_on_value = request.GET.get(self.origin_field)
super().__init__(request, params, model, model_admin)

Check warning on line 154 in core/admin/filters.py

View check run for this annotation

Codecov / codecov/patch

core/admin/filters.py#L152-L154

Added lines #L152 - L154 were not covered by tests
Loading
Loading